Subversion Repositories wimsdev

Rev

Rev 8115 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*  jqmath.js:  a JavaScript module for symbolic expressions, e.g. formatted mathematical
  2.         formulas.  This file uses charset UTF-8, and requires jQuery 1.0+, jsCurry, and jqmath.css.
  3.         By default, we use Presentation MathML when available, else simple HTML and CSS.
  4.         Expressions may be constructed programmatically, or using a simple TeX-like syntax.
  5.        
  6.         To use symbolic expressions in a web page or problem domain, one must choose a set of
  7.         symbols, ensure they can be viewed by users, and specify precedences for the operator
  8.         symbols.  We use Unicode character numbers for encoding, and fonts for display.  Normally
  9.         standard characters and fonts suffice, but "private use" character numbers and custom fonts
  10.         can be used when necessary.  By default, this module currently uses standard MathML 3
  11.         precedences for operator symbols, except we omit "multiple character operator"s like && or
  12.         <=, and choose a single precedence for each of |, ^, and _.
  13.        
  14.         The algorithm to detect MathML only works after some document.body is defined and available.
  15.         Thus it should probably not be used during document loading.
  16.        
  17.         See http://mathscribe.com/author/jqmath.html for usage documentation and examples, and
  18.         jscurry.js for some coding conventions and goals.
  19.        
  20.         Copyright 2013, Mathscribe, Inc.  Dual licensed under the MIT or GPL Version 2 licenses.
  21.         See e.g. http://jquery.org/license for an explanation of these licenses.  
  22. 17/12/2017
  23. changed for multi line math outpus support:
  24.  ma-block: display:block;text-align:center --> display:inline;text-align:left
  25. */
  26.  
  27.  
  28. "use strict";
  29.  
  30.  
  31. var jqMath = function() {
  32.         var $ = jQuery, F = jsCurry;
  33.        
  34.         function M(x, y, z) /* any number of arguments */ {
  35.                 if (typeof x == 'number')       x = String(x);  // e.g. for small integers
  36.                 if (typeof x == 'string' || Array.isArray(x))   return M.sToMathE(x, y, z);
  37.                 if (x.nodeType == 1 /* Element */ && x.tagName.toLowerCase() == 'math')
  38.                         return M.eToMathE(x);
  39.                 F.err(err_M_);
  40.         }
  41.        
  42.         M.toArray1 = function(g) { return Array.isArray(g) ? g : [g]; };
  43.        
  44.         M.sign = function(x) { return x > 0 ? 1 : x < 0 ? -1 : x == 0 ? 0 : NaN; }
  45.        
  46.         M.getSpecAttrP = function(e, attrName) {
  47.                 var attrP = e.getAttributeNode(attrName);
  48.                 return attrP && /* for IE6-7: */ attrP.specified !== false ? attrP.value : null;
  49.         };
  50.         M.objToAttrs = function(obj) {
  51.                 var res = [];
  52.                 for (var p in obj)      res.push({ name: p, value: obj[p] });
  53.                 return res;
  54.         };
  55.         M.setAttrs = function(e, attrsP) /* uses attr.name/value/[specified for IE6-7] */ {
  56.                 if (attrsP && attrsP.length == null)    attrsP = M.objToAttrs(attrsP);
  57.                
  58.                 F.iter(function(attr) {
  59.                                 if (attr.specified !== false /* for IE6-7 */)
  60.                                         e.setAttribute(attr.name, attr.value);
  61.                         }, attrsP || []);
  62.                 return e;
  63.         };
  64.        
  65.         M.replaceNode = function(newNode, oldNode) /* returns newNode (unlike node.replaceChild) */
  66.         {
  67.                 oldNode.parentNode.replaceChild(newNode, oldNode);
  68.                 return newNode;
  69.         };
  70.        
  71.         M.addClass = function(e, ws) {
  72.                 // $(e).addClass(ws) doesn't seem to work for XML elements
  73.                 if (typeof e.className != 'undefined') {        // needed for old IE, works for non-XML
  74.                         var classes = e.className;
  75.                         e.className = (classes ? classes+' ' : '')+ws;
  76.                 } else {        // needed for XML, would work for non-IE
  77.                         var classes = e.getAttribute('class');
  78.                         e.setAttribute('class', (classes ? classes+' ' : '')+ws);
  79.                 }
  80.                 return e;
  81.         };
  82.         M.eToClassesS = function(e) {
  83.                 var sP = typeof e.className != 'undefined' ? e.className : e.getAttribute('class');
  84.                 return sP || '';
  85.         };
  86.         M.hasClass = function(e, w) {   // works for HTML or XML elements, unlike jQuery e.g. 1.4.3
  87.                 return (' '+M.eToClassesS(e)+' ').replace(/[\n\t]/g, ' ').indexOf(' '+w+' ') != -1;
  88.                         // we replace /[\n\t]/g to be consistent with jQuery
  89.         };
  90.        
  91.         M.inlineBlock = function(/* ... */) /* each argument is a DOM elt, jQuery object, or HTML
  92.                         string */ {
  93.                 var res$ = $('<div/>').css('display', 'inline-block');
  94.                 if (arguments.length)   res$.append.apply(res$, arguments);
  95.                 return res$[0];
  96.         };
  97.        
  98.         M.tr$ = function(/* ... */) /* each argument is a DOM elt, jQuery object, HTML string, or
  99.                         Array; each argument produces one <td> */ {
  100.                 var appendMF = $().append;
  101.                 function td$(g) { return appendMF.apply($('<td/>'), M.toArray1(g)); }
  102.                 return appendMF.apply($('<tr/>'), F.map(td$, arguments));
  103.         };
  104.        
  105.         M.mathmlNS = "http://www.w3.org/1998/Math/MathML";      // MathML namespace
  106.        
  107.         function appendMeArgs(e, args /* null/undefined, string, node, or array-like of nodes incl.
  108.                         live NodeList */, attrsP) {
  109.                 if (args == null)       ;
  110.                 else if (typeof args == 'string')
  111.                         e.appendChild(e.ownerDocument.createTextNode(args));
  112.                 else if (args.nodeType) e.appendChild(args);
  113.                 else {
  114.                         if (args.constructor != Array)  args = F.slice(args);   // for live NodeList
  115.                         F.iter(function(x) { e.appendChild(x); }, args);
  116.                 }
  117.                 return M.setAttrs(e, attrsP);
  118.         }
  119.        
  120.         var fixMathMLQ_ = false;
  121.         (function() {   // M.webkitVersion, M.operaVersion, M.msieVersion, M.mozillaVersion
  122.                 var ua = navigator.userAgent.toLowerCase(), match = ua.match(/webkit[ \/](\d+)\.(\d+)/);
  123.                 if (match) {
  124.                         M.webkitVersion = [Number(match[1]), Number(match[2])];
  125.                         fixMathMLQ_ = M.webkitVersion[0] <= 540 /*@@*/;
  126.                 } else {
  127.                         match = ua.match(/(opera)(?:.*version)?[ \/]([\w.]+)/) ||
  128.                                 ua.match(/(msie) ([\w.]+)/) ||
  129.                                 ua.indexOf("compatible") < 0 && ua.match(/(mozilla)(?:.*? rv:([\w.]+))?/);
  130.                         if (match)      M[match[1]+'Version'] = match[2] || "0";
  131.                 }
  132.         })();
  133.         if (M.msieVersion)
  134.                 document.write(
  135.                         '<object id=MathPlayer classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987">',
  136.                         '</object><?IMPORT namespace="m" implementation="#MathPlayer" ?>');
  137.         // M.MathPlayer controls whether the IE plugin MathPlayer can be used.
  138.         function checkMathMLAttrs(e) {
  139.                 if (M.MathML && ! fixMathMLQ_)  return e;
  140.                
  141.                 var tagName = e.tagName.toLowerCase(), doc = e.ownerDocument;
  142.                 function new1(k) { return doc.createElementNS(M.mathmlNS, k); }
  143.                 if (tagName == 'mi') {
  144.                         if (! e.getAttribute('mathvariant') && e.firstChild
  145.                         && e.firstChild.nodeType == 3 /* Text */)
  146.                                 e.setAttribute('mathvariant',
  147.                                         e.firstChild.data.length == 1 ? 'italic' : 'normal');
  148.                 } else if (tagName == 'mo') {
  149.                         if (e.childNodes.length == 1 && e.firstChild.nodeType == 3 /* Text */) {
  150.                                 var s = e.firstChild.data;
  151.                                 if (/^[\u2061-\u2064]$/.test(s))        M.addClass(e, 'ma-non-marking');
  152.                                 /*@@ move parse_mxP_tokP fm- operator spacing here!? */
  153.                         }
  154.                 } else if (tagName == 'mspace') {       //@@ combine with M.newMe 'mspace' clause
  155.                         if (M.webkitVersion && M.MathML) {
  156.                                 e.style.display = 'inline-block';
  157.                                 e.style.minWidth = e.getAttribute('width') || '0px';
  158.                         }
  159.                 } else if (tagName == 'menclose') {
  160.                         if (M.webkitVersion && M.MathML)        M.addClass(e, 'fm-menclose');
  161.                 } else if (tagName == 'mmultiscripts') {
  162.                         if (M.webkitVersion) {
  163.                                 var a = F.slice(e.childNodes);  // partly because e.childNodes is dynamic
  164.                                 if (a.length == 0)      throw 'Wrong number of <mmultiscripts> arguments: 0';
  165.                                 var rowA = [a[0]];
  166.                                 for (var i = 1; i < a.length; i++) {
  167.                                         if (a[i].tagName == 'mprescripts') {
  168.                                                 rowA.unshift(new1('none'));
  169.                                                 continue;
  170.                                         }
  171.                                         if (i + 1 == a.length)  throw 'Missing argument in <mmultiscripts>';
  172.                                         var a3 = [rowA[0], a[i], a[i + 1]];
  173.                                         i++;
  174.                                         rowA[0] = new1('msubsup');
  175.                                         F.iter(function(arg) { rowA[0].appendChild(arg); }, a3);
  176.                                 }
  177.                                 var oldE = e;
  178.                                 e = appendMeArgs(new1('mrow'), rowA, e.attributes);
  179.                                 if (oldE.parentNode)    oldE.parentNode.replaceChild(e, oldE);
  180.                         }
  181.                 }
  182.                
  183.                 var colorOpt = e.getAttribute('mathcolor'), hrefOpt = e.getAttribute('href');
  184.                 if (colorOpt && e.style)        e.style.color = colorOpt;
  185.                 if (hrefOpt && (! M.MathML || M.webkitVersion)) {
  186.                         var aE = doc.createElement('A'), parentP = e.parentNode, nextP = e.nextSibling;
  187.                         aE.appendChild(e);
  188.                         aE.href = hrefOpt;
  189.                         e = aE;
  190.                         if (parentP)    parentP.insertBefore(e, nextP);
  191.                 }
  192.                 return e;
  193.         }
  194.         function newMeNS(tagName, argsP /* for appendMeArgs() */, attrsP, doc)
  195.                         /* tries to use the MathML namespace, perhaps with prefix 'm' */ {
  196.                 if (! doc)      doc = document;
  197.                
  198.                 var e = M.MathPlayer ? doc.createElement('m:'+tagName) :
  199.                         doc.createElementNS(M.mathmlNS, tagName);
  200.                 return checkMathMLAttrs(appendMeArgs(e, argsP, attrsP));
  201.         }
  202.         // M.MathML controls whether MathML is used.
  203.         (function() {
  204.                 if (self.location) {
  205.                         var match = location.search.match(/[?&;]mathml=(?:(off|false)|(on|true))\b/i);
  206.                         if (match)      M.MathML = ! match[1];
  207.                         else if (M.webkitVersion && F.cmpLex(F.cmpX, M.webkitVersion, [537, 17]) < 0
  208.                         || M.operaVersion)
  209.                                 M.MathML = false;
  210.                 }
  211.         })();
  212.         M.canMathML = function() /* requires document.body */ {
  213.                 if (M.msieVersion && ! M.MathPlayer)
  214.                         try {
  215.                                 new ActiveXObject("MathPlayer.Factory.1");
  216.                                 if (M.MathPlayer == null)       M.MathPlayer = true;
  217.                                 else if (! M.MathPlayer)        return false;   // for Carlisle's MathPlayer 3
  218.                         } catch(exc) {
  219.                                 M.MathPlayer = false;
  220.                         }
  221.                 if (! M.MathPlayer && typeof document.createElementNS == 'undefined')   return false;
  222.                
  223.                 function mathMeToE(mathMe) {
  224.                         mathMe.setAttribute('display', 'block');
  225.                         return $('<div/>').append(mathMe)[0];
  226.                 }
  227.                 var e1 = newMeNS('math', newMeNS('mn', '1')),
  228.                         e2 = newMeNS('math', newMeNS('mfrac', [newMeNS('mn', '1'), newMeNS('mn', '2')])),
  229.                         es$ = $(F.map(mathMeToE, [e1, e2]));
  230.                 es$.css('visibility', 'hidden').appendTo(document.body);
  231.                 var res = $(es$[1]).height() > $(es$[0]).height() + 2;
  232.                 es$.remove();
  233.                 return res;
  234.         };
  235.        
  236.         // fmUp is mid-x to outer top, fmDn is mid-x to outer bottom, both approx. & in parent ems
  237.         function checkVertStretch(up, dn, g, doP) /* non-MathML */ {
  238.                 if (g.nodeName.toLowerCase() == 'mo' && g.childNodes.length == 1) {
  239.                         var c = g.firstChild, s = c.data;
  240.                         if (c.nodeType == 3 /* Text */ && (up > 0.9 || dn > 0.9)
  241.                         && (M.prefix_[s] < 25 || M.postfix_[s] < 25
  242.                                         || '|\u2016\u221A' /* ‖ &radic; */.indexOf(s) != -1 || doP)) {
  243.                                 var r = (up + dn) / 1.2, radicQ = s == '\u221A',
  244.                                         v = (radicQ ? 0.26 : 0.35) + ((radicQ ? 0.15 : 0.25) - dn) / r;
  245.                                 g.style.fontSize = r.toFixed(3)+'em';
  246.                                 g.style.verticalAlign = v.toFixed(3)+'em';
  247.                                 g.fmUp = up;
  248.                                 g.fmDn = dn;
  249.                                 g.style.display = 'inline-block';
  250.                                 g.style.transform = g.style.msTransform = g.style.MozTransform =
  251.                                         g.style.WebkitTransform = 'scaleX(0.5)';
  252.                         }
  253.                 }
  254.         }
  255.         function vertAlignE$(up, dn, fmVert) /* non-MathML */ {
  256.                 var e$ = $('<span/>').append(fmVert);
  257.                 e$[0].fmUp = up;
  258.                 e$[0].fmDn = dn;
  259.                 e$[0].style.verticalAlign = (0.5 * (up - dn)).toFixed(3)+'em';
  260.                 return e$;
  261.         }
  262.         M.mtagName = function(e) /* returns 'fmath' for non-MathML 'math' */ {
  263.                 if (e.tagName == 'A' && e.childNodes.length == 1)       e = e.firstChild;
  264.                 return e.getAttribute('mtagname') || e.tagName.toLowerCase().replace(/^m:/, '');
  265.         };
  266.         M.mchilds = function(e) {
  267.                 if (e.tagName == 'A' && e.childNodes.length == 1)       e = e.firstChild;
  268.                 var mSP = e.getAttribute('mtagname');
  269.                 while (e.tagName == 'SPAN')     e = e.firstChild;
  270.                 function span0(g) { g.tagName == 'SPAN' || F.err(err_span0_); return g.firstChild; }
  271.                 if (e.tagName == 'TABLE') {
  272.                         e = e.firstChild;
  273.                         e.tagName == 'TBODY' || F.err(err_mchilds_tbody_);
  274.                         if (mSP == 'mtable')    return e.childNodes;
  275.                         var a = e.childNodes;   // <tr>s for mfrac munder mover munderover
  276.                         if (mSP == 'mover')     a = [a[1], a[0]];
  277.                         else if (mSP == 'munderover')   a = [a[1], a[2], a[0]];
  278.                         return F.map(function(tr) { return tr.firstChild.firstChild; }, a);
  279.                 } else if (e.tagName == 'MROW' && mSP) {
  280.                         var a = e.childNodes;
  281.                         if (mSP == 'msqrt')     return [span0(span0(a[1]))];
  282.                         if (mSP == 'mroot')     return [span0(span0(a[2])), span0(a[0])];
  283.                         mSP == 'mmultiscripts' || F.err(err_mchilds_mrow_);
  284.                         var nPrescripts = Number(e.getAttribute('nprescripts'));
  285.                         0 <= nPrescripts && nPrescripts < a.length && nPrescripts % 2 == 0 ||
  286.                                 F.err(err_mchilds_mmultiscripts_);
  287.                         var res = [a[nPrescripts]];
  288.                         for (var i = nPrescripts + 1; i < a.length; i++)        res.push(span0(a[i]));
  289.                         if (nPrescripts) {
  290.                                 res.push(e.ownerDocument.createElement('mprescripts'));
  291.                                 for (var i = 0; i < nPrescripts; i++)   res.push(span0(a[i]));
  292.                         }
  293.                         return res;
  294.                 } else if (F.elem(e.tagName, ['MSUB', 'MSUP', 'MSUBSUP']))
  295.                         return F.map(function(c, i) { return i ? span0(c) : c; }, e.childNodes);
  296.                 else if (e.tagName == 'MSPACE') return [];
  297.                 else    return e.childNodes;
  298.         };
  299.         var mtokens_ = ['mn', 'mi', 'mo', 'mtext', 'mspace', 'ms'],
  300.                 impMRows_ =
  301.                         ['fmath', 'msqrt', 'mtd', 'mstyle', 'merror', 'mpadded', 'mphantom', 'menclose'],
  302.                 accentDsByS_ = {        // top and bottom space in ems
  303.                         '\xAF' /* ¯ macron */: [0, 0.85], '\u203E' /* ‾ overline */: [0, 0.85],
  304.                         '\u02D9' /* ˙ dot above */: [0, 0.75], '\u02C7' /* ˇ caron */: [0, 0.7],
  305.                         '^': [0, 0.5], '~': [0, 0.4], '\u2192' /* → rightwards arrow */: [0.25, 0.25],
  306.                         '_': [0.7, 0],
  307.                         // not accents in MathML 3 operator dictionary:
  308.                         '\u2212' /* − */: [0.25, 0.45], '.': [0.6, 0.1]
  309.                 };
  310.         M.newMe = function(tagName, argsP /* for appendMeArgs() */, attrsP, docP) {
  311.                 if (! docP) {
  312.                         if (attrsP && attrsP.nodeType == 9 /* Document */) {
  313.                                 docP = attrsP;
  314.                                 attrsP = null;
  315.                         } else  docP = document;
  316.                 }
  317.                 M.MathML != null || F.err(err_newMe_MathML_);
  318.                
  319.                 if (M.MathML)   return newMeNS(tagName, argsP, attrsP, docP);
  320.                
  321.                 if (tagName == 'math')  tagName = 'fmath';
  322.                 var e$ = $(appendMeArgs(docP.createElement(tagName.toUpperCase()), argsP)),
  323.                         a = F.slice(e$[0].childNodes);  // partly because e$[0].childNodes is dynamic
  324.                 if (F.elem(tagName, impMRows_) && a.length != 1) {
  325.                         a = [M.newMe('mrow', a, null, docP)];
  326.                         e$[0].childNodes.length == 0 || F.err(err_newMe_imp_mrow_);
  327.                         e$.append(a[0]);
  328.                 }
  329.                 var ups = F.map(function(g) { return Number(g.fmUp || 0.6); }, a),
  330.                         dns = F.map(function(g) { return Number(g.fmDn || 0.6); }, a);
  331.                
  332.                 if (tagName == 'fmath' || tagName == 'mn' || tagName == 'mtext' || tagName == 'mstyle'
  333.                 || tagName == 'merror' || tagName == 'mpadded' || tagName == 'mphantom'
  334.                 || tagName == 'menclose' || tagName == 'mprescripts' || tagName == 'none')
  335.                         ;
  336.                 else if (tagName == 'mi') {
  337.                         var c = a.length == 1 ? a[0] : {};
  338.                         if (c.nodeType == 3 /* Text */ && c.data.length == 1) {
  339.                                 e$.addClass('fm-mi-length-1');
  340.                                 if ('EFHIJKMNTUVWXYZdfl'.indexOf(c.data) != -1)
  341.                                         e$.css('padding-right', '0.44ex');
  342.                         }
  343.                 } else if (tagName == 'mo') {
  344.                         var c = a.length == 1 ? a[0] : {};
  345.                         if (c.nodeType == 3 /* Text */ && /[\]|([{‖)}]/.test(c.data))
  346.                                 e$.addClass('fm-mo-Luc');
  347.                 } else if (tagName == 'mspace') {
  348.                         var e = M.setAttrs(e$[0], attrsP);
  349.                         attrsP = null;
  350.                         e.style.marginRight = e.getAttribute('width') || '0px'; // may be negative
  351.                         e.style.paddingRight = '0.001em';       // Firefox work-around
  352.                         e$.append('\u200C' /* &zwnj; */);       // for Safari/Chrome
  353.                         e$.css('visibility', 'hidden'); // e.g. for iPad Mobile Safari 4.0.4
  354.                 } else if (tagName == 'mrow') {
  355.                         var up = F.applyF(Math.max, ups), dn = F.applyF(Math.max, dns);
  356.                         if (up > 0.65 || dn > 0.65) {
  357.                                 e$[0].fmUp = up;
  358.                                 e$[0].fmDn = dn;
  359.                                 F.iter(F([up, dn, F._, null], checkVertStretch), a);
  360.                         }
  361.                 } else if (tagName == 'mfrac') {
  362.                         if (a.length != 2)      throw 'Wrong number of <mfrac> arguments: '+a.length;
  363.                         var num$ = $('<td class="fm-num-frac fm-inline"></td>', docP).append(a[0]),
  364.                                 den$ = $('<td class="fm-den-frac fm-inline"></td>', docP).append(a[1]);
  365.                         e$ = vertAlignE$(ups[0] + dns[0] + 0.03, ups[1] + dns[1] + 0.03,
  366.                                 $('<span class="fm-vert fm-frac"></span>', docP).
  367.                                                 // <span> partly for IE6-7, see www.quirksmode.org/css/display.html
  368.                                         append($('<table/>', docP).
  369.                                                 append($('<tbody/>', docP).
  370.                                                         append($('<tr/>', docP).append(num$)).
  371.                                                         append($('<tr/>', docP).append(den$))))).
  372.                                 attr('mtagname', tagName);
  373.                 } else if (tagName == 'msqrt' || tagName == 'mroot') {
  374.                         if (a.length != (tagName == 'msqrt' ? 1 : 2))
  375.                                 throw 'Wrong number of <'+tagName+'> arguments: '+a.length;
  376.                         e$ = $('<mrow/>', docP).attr('mtagname', tagName);
  377.                         var t = 0.06*(ups[0] + dns[0]), up = ups[0] + t + 0.1, dn = dns[0];
  378.                         if (tagName == 'mroot') {
  379.                                 var ht = 0.6 * (ups[1] + dns[1]), d = 0.25/0.6 - 0.25;
  380.                                 if (up > ht)    d += up/0.6 - ups[1];
  381.                                 else {
  382.                                         d += dns[1];
  383.                                         up = ht;
  384.                                 }
  385.                                 e$.append($('<span class="fm-root fm-inline"></span>', docP).append(a[1]).
  386.                                                 css('verticalAlign', d.toFixed(2)+'em'));
  387.                         }
  388.                         var mo$ = $('<mo/>', docP).addClass('fm-radic').append('\u221A' /* &radic; */),
  389.                                         // IE8 doesn't like $('<mo class="fm-radic"></mo>').append(...)
  390.                                 y$ = vertAlignE$(up, dn,
  391.                                         $('<span class="fm-vert fm-radicand"></span>', docP).append(a[0]).
  392.                                                 css('borderTopWidth', t.toFixed(3)+'em'));
  393.                         checkVertStretch(up, dn, mo$[0]);
  394.                         e$.append(mo$).append(y$);
  395.                         e$[0].fmUp = up;
  396.                         e$[0].fmDn = dn;
  397.                 } else if (tagName == 'msub' || tagName == 'msup' || tagName == 'msubsup'
  398.                 || tagName == 'mmultiscripts') {
  399.                         if (tagName != 'mmultiscripts' && a.length != (tagName == 'msubsup' ? 3 : 2))
  400.                                 throw 'Wrong number of <'+tagName+'> arguments: '+a.length;
  401.                         var up = ups[0], dn = dns[0], oddQ = tagName == 'msup',
  402.                                 dUp = up/0.71 - 0.6, dDn = dn/0.71 - 0.6;
  403.                         for (var i = 1; i < a.length; i++) {
  404.                                 if (tagName == 'mmultiscripts') {
  405.                                         var w = M.mtagName(a[i]);
  406.                                         if (w == 'none')        continue;
  407.                                         if (w == 'mprescripts') {
  408.                                                 if (oddQ)       throw 'Repeated "mprescripts"';
  409.                                                 oddQ = true;
  410.                                                 continue;
  411.                                         }
  412.                                 }
  413.                                 if (i % 2 == (oddQ ? 0 : 1))    dDn = Math.max(dDn, ups[i]);
  414.                                 else    dUp = Math.max(dUp, dns[i]);
  415.                         }
  416.                         var preAP = null, postA = [], nPrescripts = 0;
  417.                         for (var i = 1; i < a.length; i++) {
  418.                                 if (tagName == 'mmultiscripts') {
  419.                                         var w = M.mtagName(a[i]);
  420.                                         if (w == 'mprescripts') {
  421.                                                 preAP = [];
  422.                                                 nPrescripts = a.length - i - 1;
  423.                                                 continue;
  424.                                         }
  425.                                 }
  426.                                 var d = 0.25/0.71 - 0.25;
  427.                                 if (i % 2 == (preAP ? 0 : 1) && tagName != 'msup') {
  428.                                         d -= dDn;
  429.                                         dn = Math.max(dn, 0.71*(dDn + dns[i]));
  430.                                 } else {
  431.                                         d += dUp;
  432.                                         up = Math.max(up, 0.71*(dUp + ups[i]));
  433.                                 }
  434.                                 $(a[i]).wrap('<span class="fm-script fm-inline"></span>').parent().
  435.                                         css('verticalAlign', d.toFixed(2)+'em');
  436.                                 if (M.msieVersion && (document.documentMode || M.msieVersion) < 8)
  437.                                         a[i].style.zoom = 1;    // to set hasLayout
  438.                                 if (tagName == 'mmultiscripts') (preAP || postA).push(a[i].parentNode);
  439.                         }
  440.                         if (tagName == 'mmultiscripts')
  441.                                 e$ = $('<mrow/>').      // $('<mrow mtagname="mmultiscripts"></mrow>') fails in IE8
  442.                                         append($((preAP || []).concat(a[0], postA))).
  443.                                         attr({ mtagname: 'mmultiscripts', nprescripts: nPrescripts });
  444.                         e$[0].fmUp = up;
  445.                         e$[0].fmDn = dn;
  446.                 } else if (tagName == 'munder' || tagName == 'mover' || tagName == 'munderover') {
  447.                         if (a.length != (tagName == 'munderover' ? 3 : 2))
  448.                                 throw 'Wrong number of <'+tagName+'> arguments: '+a.length;
  449.                         var tbody$ = $('<tbody/>', docP), td$, up = 0.85 * ups[0], dn = 0.85 * dns[0];
  450.                         if (tagName != 'munder') {
  451.                                 var overE = a[a.length - 1], accentDsP = null;
  452.                                 td$ = $('<td/>', docP).append(overE);
  453.                                 if (overE.nodeName == 'MO' && overE.childNodes.length == 1) {
  454.                                         var c = overE.firstChild;
  455.                                         if (c.nodeType == 3 /* Text */) accentDsP = accentDsByS_[c.data];
  456.                                 }
  457.                                 if (accentDsP) {
  458.                                         overE.style.display = 'block';
  459.                                         overE.style.marginTop = (- accentDsP[0]).toFixed(2) + 'em';
  460.                                         overE.style.marginBottom = (- accentDsP[1]).toFixed(2) + 'em';
  461.                                         up += 1.2 - F.sum(accentDsP);
  462.                                 } else {
  463.                                         td$.addClass("fm-script fm-inline");
  464.                                         up += 0.71 * (ups[a.length - 1] + dns[a.length - 1]);
  465.                                 }
  466.                                 tbody$.append($('<tr/>', docP).append(td$));
  467.                         }
  468.                         if (a[0].nodeName == 'MI' && a[0].childNodes.length == 1) {
  469.                                 var c = a[0].firstChild, s = c.data;
  470.                                 if (c.nodeType == 3 /* Text */ && s.length == 1) {
  471.                                         var d = 'acegmnopqrsuvwxyz'.indexOf(s) != -1 ? 0.25 : s == 't' ? 0.15 : 0;
  472.                                         if (d) {
  473.                                                 a[0].style.display = 'block';
  474.                                                 a[0].style.marginTop = (- d).toFixed(2) + 'em';
  475.                                                 up -= d;
  476.                                         }
  477.                                 }
  478.                         }
  479.                         td$ = $('<td class="fm-underover-base"></td>', docP).append(a[0]);
  480.                         tbody$.append($('<tr/>', docP).append(td$));
  481.                         if (tagName != 'mover') {
  482.                                 td$ = $('<td class="fm-script fm-inline"></td>', docP).append(a[1]);
  483.                                 tbody$.append($('<tr/>', docP).append(td$));
  484.                                 dn += 0.71 * (ups[1] + dns[1]);
  485.                         }
  486.                         e$ = vertAlignE$(up, dn, $('<span class="fm-vert"></span>', docP).
  487.                                         append($('<table/>', docP).append(tbody$))).
  488.                                 attr('mtagname', tagName);
  489.                 } else if (tagName == 'mtable') {
  490.                         var tbody$ = $('<tbody/>', docP).append($(a));
  491.                         e$ = $('<span class="fm-vert" mtagname="mtable"></span>', docP)
  492.                                 .append($('<table/>', docP).append(tbody$));
  493.                         var r = F.sum(ups) + F.sum(dns);
  494.                         e$[0].fmUp = e$[0].fmDn = 0.5 * r;      // binomScan() may modify
  495.                 } else if (tagName == 'mtr') {
  496.                         e$ = $('<tr class="fm-mtr" mtagname="mtr"></tr>', docP).append($(a));
  497.                         var up = 0.6, dn = 0.6;
  498.                         F.iter(function(e, i) {
  499.                                         if ((e.getAttribute(M.MathML ? 'rowspan' : 'rowSpan') || 1) == 1) {
  500.                                                 up = Math.max(up, ups[i]);
  501.                                                 dn = Math.max(dn, dns[i]);
  502.                                         }
  503.                                 }, a);
  504.                         e$[0].fmUp = up + 0.25;
  505.                         e$[0].fmDn = dn + 0.25;
  506.                 } else if (tagName == 'mtd') {  // return <td> partly so rowspan/colspan work
  507.                         e$ = $('<td class="fm-mtd" mtagname="mtd"></td>', docP).append($(a));
  508.                         if (ups[0] > 0.65)      e$[0].fmUp = ups[0];
  509.                         if (dns[0] > 0.65)      e$[0].fmDn = dns[0];
  510.                         var e = M.setAttrs(e$[0], attrsP);
  511.                         attrsP = null;
  512.                         var rowspan = e.getAttribute('rowspan'), colspan = e.getAttribute('columnspan');
  513.                         if (rowspan) {
  514.                                 e.setAttribute('rowSpan', rowspan);     // for IE6-7
  515.                                 if (! M.hasClass(e, 'middle'))  M.addClass(e, 'middle');
  516.                         }
  517.                         if (colspan)    e.setAttribute('colSpan', colspan);
  518.                 } else if (tagName == 'mfenced') {
  519.                         var e = M.setAttrs(e$[0], attrsP);
  520.                         return M.newMe('mrow', M.mfencedToMRowArgs(e), attrsP, docP);
  521.                 } else  throw 'Unrecognized or unimplemented MathML tagName: '+tagName;
  522.                
  523.                 return checkMathMLAttrs(M.setAttrs(e$[0], attrsP));
  524.         };
  525.         M.mfencedToMRowArgs = function(e) {
  526.                 e.tagName.toLowerCase() == 'mfenced' || F.err(err_mfencedToMRowArgs_);
  527.                 var doc = e.ownerDocument;
  528.                 function newMo(s) { return M.newMe('mo', s, null, doc); }
  529.                 var openSP = M.getSpecAttrP(e, 'open'), closeSP = M.getSpecAttrP(e, 'close'),
  530.                         res = [newMo(openSP == null ? '(' : openSP),
  531.                                 newMo(closeSP == null ? ')' : closeSP)],
  532.                         es = F.slice(e.childNodes);
  533.                 if (es.length == 0)     return res;
  534.                 var inner;
  535.                 if (es.length == 1)     inner = es[0];
  536.                 else {
  537.                         var sepsSP = M.getSpecAttrP(e, 'separators'),
  538.                                 sepsP = (sepsSP == null ? ',' : sepsSP).match(/\S/g),
  539.                                 n = sepsP ? es.length - 1 : 0;
  540.                         for (var i = 0; i < n; i++)
  541.                                 es.splice(2*i + 1, 0, newMo(sepsP[Math.min(i, sepsP.length - 1)]));
  542.                         inner = M.newMe('mrow', es, null, doc);
  543.                 }
  544.                 res.splice(1, 0, inner);
  545.                 return res;
  546.         };
  547.         M.spaceMe = function(widthS, docP) /* incl. avoid bad font support for \u2009 &thinsp; */
  548.                 /* E.g. in Firefox 3.6.12, the DOM/jQuery don't like '' as a <mi>/<mo>/<mtext> child,
  549.                         and also padding doesn't seem to work on e.g. <mn>/<mrow>/<mspace> elements;
  550.                         also any kind of unicode space inside an <mo> might be stripped by the browser: */
  551.                 { return M.newMe('mspace', null, { width: widthS }, docP); };
  552.         M.fenceMe = function(me1, leftP, rightP, docP)
  553.                 { return M.newMe('mrow',
  554.                         [M.newMe('mo', leftP == null ? '(' : leftP, docP), me1,
  555.                                 M.newMe('mo', rightP == null ? ')' : rightP, docP)],
  556.                         docP); };
  557.         F.iter(function(tagName) { M[tagName] = F(M.newMe, tagName); },
  558.                 ['mn', 'mi', 'mo', 'mtext', 'mspace', 'mrow', 'mfenced', 'mfrac', 'msqrt', 'mroot',
  559.                         'msub', 'msup', 'msubsup', 'mmultiscripts', 'mprescripts', 'none',
  560.                         'munder', 'mover', 'munderover', 'mtable', 'mtr', 'mtd']);
  561.         M.setMathBlockQ = function(e, blockQ) {
  562.                 if (blockQ) {
  563.                         e.setAttribute('display', 'inline');
  564.                         M.addClass(e, 'ma-block');
  565.                 } else if (! M.MathML)  $(e).addClass('fm-inline');
  566.                 return e;
  567.         };
  568.         M.math = function(argsP /* for appendMeArgs() */, blockQ, docP)
  569.                 { return M.setMathBlockQ(M.newMe('math', argsP, docP), blockQ); };
  570.        
  571.         M.eToMathE = function(mathE) /* 'mathE' has been parsed from MathML syntax */ {
  572.                 if (M.MathML == null || mathE.tagName.toLowerCase() != 'math')  F.err(err_eToMathE_);
  573.                 function fixMathMLDeep(nod) {
  574.                         if (nod.nodeType != 1 /* Element */)    return nod;
  575.                         if (! F.elem(nod.tagName, mtokens_))    F.iter(fixMathMLDeep, nod.childNodes);
  576.                         return checkMathMLAttrs(nod);
  577.                 }
  578.                 if (M.MathML && mathE.tagName == 'math')
  579.                         return fixMathMLQ_ ? fixMathMLDeep(mathE) : mathE;
  580.                 var doc = mathE.ownerDocument;
  581.                 function newMeDeep(me) {
  582.                         var tagName = me.tagName.toLowerCase(), args = me.childNodes;
  583.                         function nodeToMes(nod) {
  584.                                 if (nod.nodeType == 3 /* Text */)
  585.                                         return /^\s*$/.test(nod.data) ? [] : [M.mtext(nod.data, doc)];
  586.                                 if (nod.nodeType == 8 /* Comment */)    return [];
  587.                                 me.nodeType == 1 /* Element */ || F.err(err_newMeDeep_);
  588.                                 return [newMeDeep(nod)];
  589.                         }
  590.                         if (F.elem(tagName, mtokens_)) {
  591.                                 if (tagName == 'mo' && args.length == 1 && args[0].nodeType == 3 /* Text */
  592.                                 && args[0].data == '-') args = M['-'];
  593.                         } else  args = F.concatMap(nodeToMes, args);
  594.                         var res = M.newMe(tagName, args, me.attributes, doc);
  595.                         if (tagName == 'math')  M.setMathBlockQ(res, me.getAttribute('display') == 'block');
  596.                         return res;
  597.                 }
  598.                 return newMeDeep(mathE);
  599.         };
  600.        
  601.         M['-'] = '\u2212';      // &minus; −
  602.         M.trimNumS = function(s) /* trims trailing 0s after decimal point, trailing ., -0 */ {
  603.                 return s.replace(/(\d\.\d*?)0+(?!\d)/g, '$1').replace(/(\d)\.(?!\d)/g, '$1').
  604.                         replace(/[-\u2212]0(?![.\d])/g, '0');
  605.         };
  606.         M.numS = function(s, trimQ) /* converts a numeric string to jqMath notation */ {
  607.                 if (trimQ)      s = M.trimNumS(s);
  608.                 return s.replace(/Infinity/ig, '\u221E' /* ∞ */).replace(/NaN/ig, '{?}').
  609.                         replace(/e(-\d+)/ig, '\xB710^{$1}').replace(/e\+?(\d+)/ig, '\xB710^$1').
  610.                         replace(/-/g, M['-']);  // \xB7 is ·
  611.         };
  612.        
  613.         /*  Like TeX, we use ^ for superscripts, _ for subscripts, {} for grouping, and \ (or `) as
  614.                 an escape character.  Spaces and newline characters are ignored.  We also use ↖ (\u2196)
  615.                 and ↙ (\u2199) to put limits above and below an operator or expression.  You can
  616.                 $.extend() or even replace M.infix_, M.prefix_, M.postfix_, M.macros_, M.macro1s_, and
  617.                 M.alias_ as needed.  */
  618.         M.combiningChar_ = '[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]';
  619.         M.surrPair_ = '[\uD800-\uDBFF][\uDC00-\uDFFF]';
  620.         var decComma_, escPat_ = '[\\\\`]([A-Za-z]+|.)';
  621.         M.decimalComma = function(qP) /* whether to allow decimal commas */ {
  622.                 if (qP != null) {
  623.                         decComma_ = qP;
  624.                         var numPat = (qP ? '\\d*,\\d+|' : '') + '\\d+\\.?\\d*|\\.\\d+';
  625.                         M.re_ /* .lastIndex set during use */ = RegExp(
  626.                                 '('+numPat+')|'+escPat_+'|'+M.surrPair_+'|\\S'+M.combiningChar_+'*', 'g');
  627.                 }
  628.                 return decComma_;
  629.         };
  630.         var commaLangs = 'af|an|ar|av|az|ba|be|bg|bs|ca|ce|co|cs|cu|cv|da|de|el|es|et|eu|fi|fo|fr|'+
  631.                 'gl|hr|hu|hy|id|is|it|jv|kk|kl|kv|lb|lt|lv|mk|mn|mo|nl|no|os|pl|pt|ro|ru|sc|sk|sq|sr|'+
  632.                 'su|sv|tr|tt|ug|uk|vi|yi';
  633.         M.decimalComma(RegExp('^('+commaLangs+')\\b', 'i').test(document.documentElement.lang));
  634.         M.infix_ = {    // operator precedences, see http://www.w3.org/TR/MathML3/appendixc.html
  635.                 '⊂⃒': 240, '⊃⃒': 240,
  636.                 '≪̸': 260, '≫̸': 260, '⪯̸': 260, '⪰̸': 260,
  637.                 '∽̱': 265, '≂̸': 265, '≎̸': 265, '≏̸': 265, '≦̸': 265, '≿̸': 265, '⊏̸': 265, '⊐̸': 265, '⧏̸': 265,
  638.                 '⧐̸': 265, '⩽̸': 265, '⩾̸': 265, '⪡̸': 265, '⪢̸': 265,
  639.                
  640.                 // if non-MathML and precedence <= 270, then class is 'fm-infix-loose' not 'fm-infix'
  641.                
  642.                 /* '-' is converted to '\u2212' &minus; − */
  643.                 '\u2009' /* &thinsp; ' ', currently generates an <mspace> */: 390,
  644.                
  645.                 '' /* no <mo> is generated */: 500 /* not 390 or 850 */
  646.                
  647.                 /* \^ or `^  880 not 780, \_ or `_ 880 not 900 */
  648.                
  649.                 // unescaped ^ _ ↖ (\u2196) ↙ (\u2199) have precedence 999
  650.         };
  651.         // If an infix op is also prefix or postfix, it must use the same precedence in each form.
  652.         // Also, we omit "multiple character operator"s like && or <=.
  653.         M.prefix_ = {};
  654.                 // prefix precedence < 25 => thin space not inserted between multi-letter <mi> and it;
  655.                 //      (prefix or postfix precedence < 25) and non-MathML => <mo> stretchy;
  656.                 //      precedence < 25 => can be a fence
  657.                
  658.                 // can use {|...|} for absolute value
  659.                
  660.                 // + - % and other infix ops can automatically be used as prefix and postfix ops
  661.                
  662.                 // if non-MathML and prefix and 290 <= precedence <= 350, then 'fm-large-op'
  663.         M.postfix_ = {
  664.                 // (unquoted) ' is converted to '\u2032' &prime; ′
  665.         };
  666.         function setPrecs(precs, precCharsA) {
  667.                 F.iter(function(prec_chars) {
  668.                                 var prec = prec_chars[0];
  669.                                 F.iter(function(c) { precs[c] = prec; }, prec_chars[1].split(''));
  670.                         }, precCharsA);
  671.         }
  672.         setPrecs(M.infix_, [
  673.                         [21, '|'],      // | not 20 or 270
  674.                         [30, ';'],
  675.                         [40, ',\u2063'],
  676.                         [70, '∴∵'],
  677.                         [100, ':'],
  678.                         [110, '϶'],
  679.                         [150, '…⋮⋯⋱'],
  680.                         [160, '∋'],
  681.                         [170, '⊢⊣⊤⊨⊩⊬⊭⊮⊯'],
  682.                         [190, '∨'],
  683.                         [200, '∧'],
  684.                         [240, '∁∈∉∌⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋'],
  685.                         [241, '≤'],
  686.                         [242, '≥'],
  687.                         [243, '>'],
  688.                         [244, '≯'],
  689.                         [245, '<'],
  690.                         [246, '≮'],
  691.                         [247, '≈'],
  692.                         [250, '∼≉'],
  693.                         [252, '≢'],
  694.                         [255, '≠'],
  695.                         [260, '=∝∤∥∦≁≃≄≅≆≇≍≔≗≙≚≜≟≡≨≩≪≫≭≰≱≺≻≼≽⊀⊁⊥⊴⊵⋉⋊⋋⋌⋔⋖⋗⋘⋙⋪⋫⋬⋭■□▪▫▭▮▯▰▱△▴▵▶▷▸▹▼▽▾▿◀◁◂◃'+
  696.                                 '◄◅◆◇◈◉◌◍◎●◖◗◦⧀⧁⧣⧤⧥⧦⧳⪇⪈⪯⪰'],
  697.                         [265, '⁄∆∊∍∎∕∗∘∙∟∣∶∷∸∹∺∻∽∾∿≂≊≋≌≎≏≐≑≒≓≕≖≘≝≞≣≦≧≬≲≳≴≵≶≷≸≹≾≿⊌⊍⊎⊏⊐⊑⊒⊓⊔⊚⊛⊜⊝⊦⊧⊪⊫⊰⊱⊲⊳⊶⊷⊹⊺⊻⊼⊽⊾⊿⋄⋆⋇'+
  698.                                 '⋈⋍⋎⋏⋐⋑⋒⋓⋕⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋰⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿▲❘⦁⦂⦠⦡⦢⦣⦤⦥⦦⦧⦨⦩⦪⦫⦬⦭⦮⦯⦰⦱⦲⦳⦴⦵⦶⦷⦸⦹⦺⦻⦼⦽⦾⦿⧂⧃⧄'+
  699.                                 '⧅⧆⧇⧈⧉⧊⧋⧌⧍⧎⧏⧐⧑⧒⧓⧔⧕⧖⧗⧘⧙⧛⧜⧝⧞⧠⧡⧢⧧⧨⧩⧪⧫⧬⧭⧮⧰⧱⧲⧵⧶⧷⧸⧹⧺⧻⧾⧿⨝⨞⨟⨠⨡⨢⨣⨤⨥⨦⨧⨨⨩⨪⨫⨬⨭⨮⨰⨱⨲⨳⨴⨵⨶⨷⨸⨹'+
  700.                                 '⨺⨻⨼⨽⨾⩀⩁⩂⩃⩄⩅⩆⩇⩈⩉⩊⩋⩌⩍⩎⩏⩐⩑⩒⩓⩔⩕⩖⩗⩘⩙⩚⩛⩜⩝⩞⩟⩠⩡⩢⩣⩤⩥⩦⩧⩨⩩⩪⩫⩬⩭⩮⩯⩰⩱⩲⩳⩴⩵⩶⩷⩸⩹⩺⩻⩼⩽⩾⩿⪀⪁⪂⪃⪄⪅⪆⪉⪊⪋⪌⪍⪎⪏'+
  701.                                 '⪐⪑⪒⪓⪔⪕⪖⪗⪘⪙⪚⪛⪜⪝⪞⪟⪠⪡⪢⪣⪤⪥⪦⪧⪨⪩⪪⪫⪬⪭⪮⪱⪲⪳⪴⪵⪶⪷⪸⪹⪺⪻⪼⪽⪾⪿⫀⫁⫂⫃⫄⫅⫆⫇⫈⫉⫊⫋⫌⫍⫎⫏⫐⫑⫒⫓⫔⫕⫖⫗⫘⫙⫚⫛⫝⫝⫞⫟⫠⫡⫢⫣⫤⫥⫦'+
  702.                                 '⫧⫨⫩⫪⫫⫬⫭⫮⫯⫰⫱⫲⫳⫴⫵⫶⫷⫸⫹⫺⫻⫽⫾'],
  703.                         [270, '←↑→↓↔↕↖↗↘↙↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹↺↻↼↽↾↿⇀⇁⇂⇃⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑'+
  704.                                 '⇒⇓⇔⇕⇖⇗⇘⇙⇚⇛⇜⇝⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪⇫⇬⇭⇮⇯⇰⇱⇲⇳⇴⇵⇶⇷⇸⇹⇺⇻⇼⇽⇾⇿⊸⟰⟱⟵⟶⟷⟸⟹⟺⟻⟼⟽⟾⟿⤀⤁⤂⤃⤄'+
  705.                                 '⤅⤆⤇⤈⤉⤊⤋⤌⤍⤎⤏⤐⤑⤒⤓⤔⤕⤖⤗⤘⤙⤚⤛⤜⤝⤞⤟⤠⤡⤢⤣⤤⤥⤦⤧⤨⤩⤪⤫⤬⤭⤮⤯⤰⤱⤲⤳⤴⤵⤶⤷⤸⤹⤺⤻⤼⤽⤾⤿⥀⥁⥂⥃⥄⥅⥆⥇⥈⥉⥊⥋⥌⥍⥎⥏⥐⥑⥒'+
  706.                                 '⥓⥔⥕⥖⥗⥘⥙⥚⥛⥜⥝⥞⥟⥠⥡⥢⥣⥤⥥⥦⥧⥨⥩⥪⥫⥬⥭⥮⥯⥰⥱⥲⥳⥴⥵⥶⥷⥸⥹⥺⥻⥼⥽⥾⥿⦙⦚⦛⦜⦝⦞⦟⧟⧯⧴⭅⭆'],
  707.                         [275, '+-±−∓∔⊞⊟'],
  708.                         [300, '⊕⊖⊘'],
  709.                         [340, '≀'],
  710.                         [350, '∩∪'],
  711.                         [390, '*.ו\u2062⊠⊡⋅⨯⨿'],
  712.                         [400, '·'],
  713.                         [410, '⊗'],
  714.                         [640, '%'],
  715.                         [650, '\\∖'],
  716.                         [660, '/÷'],
  717.                         [710, '⊙'],
  718.                         [825, '@'],
  719.                         [835, '?'],
  720.                         [850, '\u2061'],
  721.                         [880, '^_\u2064']]);
  722.         setPrecs(M.prefix_, [
  723.                         [10, '‘“'],
  724.                         [20, '([{‖⌈⌊❲⟦⟨⟪⟬⟮⦀⦃⦅⦇⦉⦋⦍⦏⦑⦓⦕⦗⧼'],
  725.                         [230, '∀∃∄'],
  726.                         [290, '∑⨊⨋'],
  727.                         [300, '∬∭⨁'],
  728.                         [310, '∫∮∯∰∱∲∳⨌⨍⨎⨏⨐⨑⨒⨓⨔⨕⨖⨗⨘⨙⨚⨛⨜'],
  729.                         [320, '⋃⨃⨄'],
  730.                         [330, '⋀⋁⋂⨀⨂⨅⨆⨇⨈⨉⫼⫿'],
  731.                         [350, '∏∐'],
  732.                         [670, '∠∡∢'],
  733.                         [680, '¬'],
  734.                         [740, '∂∇'],
  735.                         [845, 'ⅅⅆ√∛∜']]);
  736.         setPrecs(M.postfix_, [
  737.                         [10, '’”'],
  738.                         [20, ')]}‖⌉⌋❳⟧⟩⟫⟭⟯⦀⦄⦆⦈⦊⦌⦎⦐⦒⦔⦖⦘⧽'],
  739.                         [800, '′♭♮♯'],
  740.                         [810, '!'],
  741.                         [880, '&\'`~¨¯°´¸ˆˇˉˊˋˍ˘˙˚˜˝˷\u0302\u0311‾\u20db\u20dc⎴⎵⏜⏝⏞⏟⏠⏡']]);
  742.        
  743.         var s_, s_or_mx_a_, s_or_mx_i_, docP_, precAdj_;
  744.         /*  A "tok" (scanner token or similar) here is an [meP, opSP], with at least one != null.
  745.                 The meP may be non-atomic.  Thus a tok is either an me, possibly with a precedence given
  746.                 by an operator, or else either a meta-operator or "macro1" for building an me.  */
  747.        
  748.         function newMe_(tagName, argsP) { return M.newMe(tagName, argsP, docP_); }
  749.         function emptyMe_() { return newMe_('mspace' /* or 'mrow'? */); }
  750.        
  751.         function scanWord(descP) /* use scanString() instead if any quotes should be stripped */ {
  752.                 var re = /\s*([-\w.]*)/g;       //+ could allow all unicode alphabetics
  753.                 re.lastIndex = M.re_.lastIndex;
  754.                 var match = re.exec(s_);
  755.                 if (! match[1]) throw 'Missing '+(descP || 'word');
  756.                 M.re_.lastIndex = re.lastIndex;
  757.                 return match[1];
  758.         }
  759.         function scanString(descP) /* scans a word or quoted string */ {
  760.                 var re = /\s*(?:(["'])|([-\w.]*))/g;
  761.                 re.lastIndex = M.re_.lastIndex;
  762.                 var match = re.exec(s_);
  763.                 if (match[2]) {
  764.                         M.re_.lastIndex = re.lastIndex;
  765.                         return match[2];
  766.                 }
  767.                 if (! match[1]) throw 'Missing '+(descP || 'string');
  768.                 var c = match[1], re2 = RegExp('[^\\`'+c+']+|[\\`](.|\n)|('+c+')', 'g'), res = '';
  769.                 re2.lastIndex = re.lastIndex;
  770.                 while (true) {
  771.                         match = re2.exec(s_);
  772.                         if (! match)    throw 'Missing closing '+c;
  773.                         if (match[2])   break;
  774.                         res += match[1] || match[0];
  775.                 }
  776.                 M.re_.lastIndex = re2.lastIndex;
  777.                 return res;
  778.         }
  779.         function scanMeTok(afterP) {
  780.                 var tokP = scanTokP();
  781.                 if (! tokP || ! tokP[0])
  782.                         throw 'Missing expression argument'+(afterP ? ' after '+afterP : '')+
  783.                                 ', before position '+M.re_.lastIndex;
  784.                 return tokP;
  785.         }
  786.        
  787.         function mtokenScan(tagName /* 'mn', 'mi', 'mo', or 'mtext' */) {
  788.                 var s = scanString(tagName == 'mtext' ? 'text' : tagName);
  789.                 return [newMe_(tagName, s), tagName == 'mo' ? s : null];
  790.         }
  791.         function htmlScan() {
  792.                 var h = scanString('html'),
  793.                         e = $('<div/>', docP_ || document).css('display', 'inline-block').html(h)[0];
  794.                 if (e.childNodes.length == 1)   e = e.childNodes[0];
  795.                 return [newMe_('mtext', e), null];
  796.         }
  797.         function spScan() {
  798.                 var widthS = scanString('\\sp width');
  799.                 return [M.spaceMe(widthS, docP_),
  800.                         /^[^-]*[1-9]/.test(widthS) ? '\u2009' /* &thinsp; */ : null];
  801.         }
  802.         function braceScan() {
  803.                 var tokP = scanTokP();
  804.                 if (tokP && tokP[1] == '↖' && ! tokP[0]) {    // grouped op
  805.                         var meTokP_tokP = parseEmbel();
  806.                         tokP = meTokP_tokP[1] || scanTokP();
  807.                         if (! (meTokP_tokP[0] && tokP && tokP[1] == '}' && ! tokP[0]))
  808.                                 throw 'Expected an embellished operator and "}" after "{↖", before position '+
  809.                                         M.re_.lastIndex;
  810.                         return meTokP_tokP[0];
  811.                 }
  812.                 var mxP_tokP = parse_mxP_tokP(0, tokP);
  813.                 tokP = mxP_tokP[1];
  814.                 // if (! tokP)  throw 'Missing "}"';
  815.                 ! tokP || tokP[1] == '}' && ! tokP[0] || F.err(err_braceScan_);
  816.                 return [mxP_tokP[0] || emptyMe_(), null];
  817.         }
  818.         function attrScan(nameP, mmlOnlyQ) {    // note usually doesn't affect non-MathML rendering
  819.                 if (! nameP)    nameP = scanWord('attribute name');
  820.                 var v = scanString(nameP+' attribute'), tok = scanMeTok(nameP);
  821.                 if (! mmlOnlyQ || M.MathML)     tok[0].setAttribute(nameP, v);
  822.                 return tok;
  823.         }
  824.         function clScan() {     // note currently ignored by MathPlayer
  825.                 var desc = 'CSS class name(s)', ws = scanString(desc), tok = scanMeTok(desc);
  826.                 M.addClass(tok[0], ws);
  827.                 return tok;
  828.         }
  829.         // see http://www.w3.org/TR/MathML3/chapter3.html#presm.commatt for mathvariant attr
  830.         function mvScan(sP) {
  831.                 var s = sP || scanString('mathvariant'), tok = scanMeTok(s), me = tok[0];
  832.                 if (! F.elem(M.mtagName(me), ['mi', 'mn', 'mo', 'mtext', 'mspace', 'ms']))
  833.                         throw 'Can only apply a mathvariant to a MathML token (atomic) element, at '+
  834.                                 'position '+M.re_.lastIndex;
  835.                
  836.                 me.setAttribute('mathvariant', s);
  837.                
  838.                 if (/bold/.test(s))     M.addClass(me, 'ma-bold');
  839.                 else if (s == 'normal' || s == 'italic')        M.addClass(me, 'ma-nonbold');
  840.                
  841.                 M.addClass(me, /italic/.test(s) ? 'ma-italic' : 'ma-upright');
  842.                
  843.                 if (/double-struck/.test(s))    M.addClass(me, 'ma-double-struck');
  844.                 else if (/fraktur/.test(s))     M.addClass(me, 'ma-fraktur');
  845.                 else if (/script/.test(s))      M.addClass(me, 'ma-script');
  846.                 else if (/sans-serif/.test(s))  M.addClass(me, 'ma-sans-serif');
  847.                
  848.                 // (monospace, initial, tailed, looped, stretched) currently don't add classes
  849.                
  850.                 return tok;
  851.         }
  852.         function ovScan() /* overbar */ {
  853.                 return [M.newMe('menclose', scanMeTok('\\ov')[0], { notation: 'top' }, docP_), null];
  854.         }
  855.         function minsizeScan() {
  856.                 var s = scanString('minsize'), tok = scanMeTok('minsize'), me = tok[0];
  857.                 if (M.mtagName(me) != 'mo')
  858.                         throw 'Can only stretch an operator symbol, before position '+M.re_.lastIndex;
  859.                 if (M.MathML)   me.setAttribute('minsize', s);
  860.                 else {
  861.                         var r = Number(s);      // may be NaN, 0, etc.
  862.                         if (r > 1)      checkVertStretch(0.6*r, 0.6*r, me, true);
  863.                         else    me.style.fontSize = s;
  864.                 }
  865.                 return tok;
  866.         }
  867.         function mrow1Scan() { return [newMe_('mrow', scanMeTok('\\mrowOne')[0]), null]; }
  868.         function binomScan() {
  869.                 function mtr1(e) { return newMe_('mtr', newMe_('mtd', e)); }
  870.                 var xMe = scanMeTok('\\binom')[0], yMe = scanMeTok('\\binom')[0],
  871.                         zMe = newMe_('mtable', F.map(mtr1, [xMe, yMe]));
  872.                 M.addClass(zMe, 'ma-binom');
  873.                 if (! M.MathML) {
  874.                         zMe.fmUp -= 0.41;
  875.                         zMe.fmDn -= 0.41;
  876.                 }
  877.                 return [newMe_('mrow', [newMe_('mo', '('), zMe, newMe_('mo', ')')]), null];
  878.         }
  879.         M.macros_ /* each returns a tokP */ = {
  880.                 mn: F(mtokenScan, 'mn'), mi: F(mtokenScan, 'mi'), mo: F(mtokenScan, 'mo'),
  881.                 text: F(mtokenScan, 'mtext'), html: htmlScan, sp: spScan,
  882.                 attr: attrScan, attrMML: F(attrScan, null, true),
  883.                 id: F(attrScan, 'id'), dir: F(attrScan, 'dir'), cl: clScan, mv: mvScan,
  884.                 bo: F(mvScan, 'bold'), it: F(mvScan, 'italic'), bi: F(mvScan, 'bold-italic'),
  885.                 sc: F(mvScan, 'script'), bs: F(mvScan, 'bold-script'), fr: F(mvScan, 'fraktur'),
  886.                 ds: F(mvScan, 'double-struck'), bf: F(mvScan, 'bold-fraktur'), ov: ovScan,
  887.                 minsize: minsizeScan, mrowOne: mrow1Scan, binom: binomScan
  888.         };
  889.        
  890.         M.alias_ = { '-': M['-'], '\'': '\u2032' /* &prime; */,
  891.                 '\u212D': ['C', 'fraktur'], '\u210C': ['H', 'fraktur'], '\u2111': ['I', 'fraktur'],
  892.                 '\u211C': ['R', 'fraktur'], '\u2128': ['Z', 'fraktur'],
  893.                 '\u212C': ['B', 'script'], '\u2130': ['E', 'script'], '\u2131': ['F', 'script'],
  894.                 '\u210B': ['H', 'script'], '\u2110': ['I', 'script'], '\u2112': ['L', 'script'],
  895.                 '\u2133': ['M', 'script'], '\u211B': ['R', 'script'], '\u212F': ['e', 'script'],
  896.                 '\u210A': ['g', 'script'], /* '\u2113': ['l', 'script'], */ '\u2134': ['o', 'script']
  897.         };
  898.         var spaces_ = { ',': '.17em', ':': '.22em', ';': '.28em', '!': '-.17em' };
  899.         function scanTokP() {
  900.                 var match = M.re_.exec(s_);
  901.                 while (! match) {
  902.                         M.re_.lastIndex = s_.length;
  903.                         if (s_or_mx_i_ == s_or_mx_a_.length)    return null;
  904.                         var g = s_or_mx_a_[s_or_mx_i_++];
  905.                         if (typeof g == 'string') {
  906.                                 M.re_.lastIndex = 0;
  907.                                 s_ = g;
  908.                                 match = M.re_.exec(s_);
  909.                         } else if (g.nodeType == 1 /* Element */)       return [g, null];
  910.                         else    F.err(err_scanTokP_);
  911.                 }
  912.                 var s1 = match[2] || match[0], mvP = null;
  913.                 if (/^[_^}\u2196\u2199]$/.test(match[0]) || match[2] && M.macro1s_[s1])
  914.                         return [null, s1];
  915.                 if (match[0] == '{')    return braceScan();
  916.                 if (match[2] && M.macros_[s1])  return M.macros_[s1]();
  917.                 if (match[1])   return [M.newMe('mn', s1, docP_), null];
  918.                 if (/^[,:;!]$/.test(match[2]))  s1 = '\u2009' /* &thinsp; */;
  919.                 /* else if (match[2] == '&') {
  920.                         if (M.mozillaVersion && M.MathML && false /* <maligngroup> not supported yet * /)
  921.                                 return [M.newMe('maligngroup', null, docP_), null];
  922.                         s1 = ',';
  923.                 } */ else if (match[2] == '/')  s1 = '\u2215' /* ∕ */;
  924.                 else if (M.alias_[s1] && ! match[2]) {
  925.                         var t = M.alias_[s1];
  926.                         if (typeof t == 'string')       s1 = t;
  927.                         else {
  928.                                 s1 = t[0];
  929.                                 mvP = t[1];     // 'double-struck', 'fraktur', or 'script'
  930.                         }
  931.                 }
  932.                 var opSP = M.infix_[s1] || M.prefix_[s1] || M.postfix_[s1] ? s1 : null, e;
  933.                 if (s1 == '\u2009' /* &thinsp; */)      // incl. avoid bad font support, incl. in MathML
  934.                         e = M.spaceMe(spaces_[match[2] || ','], docP_);
  935.                 else if (opSP) {
  936.                         e = M.newMe('mo', s1, docP_);
  937.                         if (/^[∀∃∄∂∇]$/.test(s1)) {   // Firefox work-arounds
  938.                                 e.setAttribute('lspace', '.11em');
  939.                                 e.setAttribute('rspace', '.06em');
  940.                         } else if (s1 == '!') {
  941.                                 e.setAttribute('lspace', '.06em');
  942.                                 e.setAttribute('rspace', '0');
  943.                         } else if (s1 == '×' /* '\u00D7' */) {
  944.                                 e.setAttribute('lspace', '.22em');
  945.                                 e.setAttribute('rspace', '.22em');
  946.                         }
  947.                 } else {
  948.                         e = M.newMe('mi', s1, docP_);
  949.                         if (match[2] && s1.length == 1) {
  950.                                 e.setAttribute('mathvariant', 'normal');
  951.                                 M.addClass(e, 'ma-upright');
  952.                                 if (! M.MathML) e.style.paddingRight = '0';
  953.                         } else if (mvP) {
  954.                                 e.setAttribute('mathvariant', mvP);
  955.                                 M.addClass(e, 'ma-upright');
  956.                                 M.addClass(e, 'ma-'+mvP);
  957.                         }
  958.                         if (/\w\w/.test(s1))    M.addClass(e, 'ma-repel-adj');
  959.                 }
  960.                 return [e, opSP];
  961.         }
  962.        
  963.         function parse_mtd_tokP(optQ /* => mtd can be null or an mtr */) {
  964.                 var mxP_tokP = parse_mxP_tokP(M.infix_[',']), tokP = mxP_tokP[1] || scanTokP(),
  965.                         mxP = mxP_tokP[0];
  966.                 if (! mxP) {
  967.                         if (optQ && ! (tokP && tokP[1] == ',')) return [null, tokP];
  968.                         mxP = emptyMe_();
  969.                 }
  970.                 var w = M.mtagName(mxP);
  971.                 if (w != 'mtd' && ! (w == 'mtr' && optQ))       mxP = M.newMe('mtd', mxP, docP_);
  972.                 return [mxP, tokP];
  973.         }
  974.         function parse_rowspan_tokP() {
  975.                 var v = scanString('rowspan'), mtd_tokP = parse_mtd_tokP(), mtd = mtd_tokP[0];
  976.                 mtd.setAttribute(M.MathML ? 'rowspan' : 'rowSpan' /* for IE6-7 */, v);
  977.                 if (! M.hasClass(mtd, 'middle'))        M.addClass(mtd, 'middle');
  978.                 return mtd_tokP;
  979.         }
  980.         function parse_colspan_tokP() {
  981.                 var v = scanString('colspan'), mtd_tokP = parse_mtd_tokP();
  982.                 mtd_tokP[0].setAttribute(M.MathML ? 'columnspan' : 'colSpan', v);
  983.                 return mtd_tokP;
  984.         }
  985.         function parse_mtr_tokP(optQ /* => mtr can be null */) {
  986.                 var mtds = [];
  987.                 while (true) {
  988.                         var mtdP_tokP = parse_mtd_tokP(mtds.length == 0), mtdP = mtdP_tokP[0],
  989.                                 tokP = mtdP_tokP[1] || scanTokP();
  990.                         if (mtdP) {
  991.                                 if (M.mtagName(mtdP) == 'mtr')  return [mtdP, tokP];
  992.                                 mtds.push(mtdP);
  993.                         }
  994.                         if (! (tokP && tokP[1] == ','))
  995.                                 return [mtds.length || ! optQ || tokP && tokP[1] == ';' ?
  996.                                                 M.newMe('mtr', mtds, docP_) : null, tokP];
  997.                 }
  998.         }
  999.         function parse_table_tokP() {
  1000.                 var mtrs = [];
  1001.                 while (true) {
  1002.                         var mtrP_tokP = parse_mtr_tokP(mtrs.length == 0), mtrP = mtrP_tokP[0],
  1003.                                 tokP = mtrP_tokP[1] || scanTokP();
  1004.                         if (mtrP)       mtrs.push(mtrP);
  1005.                         if (! (tokP && tokP[1] == ';')) return [M.newMe('mtable', mtrs, docP_), tokP];
  1006.                 }
  1007.         }
  1008.         function parse_math_tokP() {
  1009.                 var mxP_tokP = parse_mxP_tokP(0);
  1010.                 mxP_tokP[0] = M.newMe('math', mxP_tokP[0], docP_);
  1011.                 return mxP_tokP;
  1012.         }
  1013.         // An "mx" here is an "me" that is not just a bare or embellished operator.
  1014.         M.macro1s_ /* each returns mxP_tokP, so can do precedence-based look-ahead */ = {
  1015.                 mtd: parse_mtd_tokP, rowspan: parse_rowspan_tokP, colspan: parse_colspan_tokP,
  1016.                 mtr: parse_mtr_tokP, table: parse_table_tokP, math: parse_math_tokP
  1017.         };
  1018.        
  1019.         var embelWs_ = { '_': 'sub', '^': 'sup', '\u2199': 'under', '\u2196': 'over' };
  1020.         function embelKP(op) {
  1021.                 var wP = embelWs_[op];
  1022.                 return wP && (wP.length < 4 ? 'ss' : 'uo');
  1023.         }
  1024.         function parseEmbel(meTokP, tokP) /* checks sub/sup/under/over; returns [meTokP, tokP] */ {
  1025.                 while (true) {
  1026.                         if (! tokP)     tokP = scanTokP();
  1027.                         if (! tokP || tokP[0] || ! embelWs_[tokP[1]]) {
  1028.                                 if (tokP && ! meTokP) {
  1029.                                         meTokP = tokP;
  1030.                                         tokP = null;
  1031.                                         continue;
  1032.                                 }
  1033.                                 return [meTokP, tokP];
  1034.                         }
  1035.                         var k = embelKP(tokP[1]),
  1036.                                 parseMxs = function() /* returns 0-2 mxs of kind 'k', by op; sets tokP */ {
  1037.                                         var mxs = {}, doneQs = {};
  1038.                                         while (true) {
  1039.                                                 if (! tokP)     tokP = scanTokP();
  1040.                                                 if (! tokP || tokP[0])  break;
  1041.                                                 var op = tokP[1];
  1042.                                                 if (embelKP(op) != k || doneQs[op])     break;
  1043.                                                 doneQs[op] = true;
  1044.                                                 tokP = scanTokP();
  1045.                                                 if (tokP && embelKP(tokP[1]) == k && ! tokP[0]) continue;
  1046.                                                 var mxP_tokP = parse_mxP_tokP(999, tokP);
  1047.                                                 mxs[op] = mxP_tokP[0];  // null ok
  1048.                                                 tokP = mxP_tokP[1];
  1049.                                         }
  1050.                                         return mxs;
  1051.                                 }, mxs = parseMxs();
  1052.                         if (k == 'uo' || ! tokP || (tokP[0] ? meTokP : embelKP(tokP[1]) != 'ss')) {
  1053.                                 if (! meTokP)   meTokP = [emptyMe_(), null];
  1054.                                 var w = 'm', a = [meTokP[0]];
  1055.                                 F.iter(function(op) {
  1056.                                                 if (mxs[op]) {
  1057.                                                         w += embelWs_[op];
  1058.                                                         a.push(mxs[op]);
  1059.                                                 }
  1060.                                         }, ['_', '^', '↙', '↖']);
  1061.                                 if (a.length > 1)       meTokP = [M.newMe(w, a, docP_), meTokP[1]];
  1062.                         } else {
  1063.                                 var mxsPA = [mxs];
  1064.                                 while (tokP && ! tokP[0] && embelKP(tokP[1]) == 'ss')   mxsPA.push(parseMxs());
  1065.                                 if (! meTokP) {
  1066.                                         if (! tokP || ! tokP[0])        meTokP = [emptyMe_(), null];
  1067.                                         else {
  1068.                                                 meTokP = tokP;
  1069.                                                 tokP = scanTokP();
  1070.                                                 var postA = [];
  1071.                                                 while (tokP && ! tokP[0] && embelKP(tokP[1]) == 'ss')
  1072.                                                         postA.push(parseMxs());
  1073.                                                 mxsPA = postA.concat(null, mxsPA);
  1074.                                         }
  1075.                                 }
  1076.                                 var a = [meTokP[0]];
  1077.                                 F.iter(function(mxsP) {
  1078.                                                 if (! mxsP)     a.push(M.newMe('mprescripts', null, docP_));
  1079.                                                 else
  1080.                                                         a.push(mxsP['_'] || M.newMe('none', null, docP_),
  1081.                                                                 mxsP['^'] || M.newMe('none', null, docP_));
  1082.                                         }, mxsPA);
  1083.                                 meTokP = [M.newMe('mmultiscripts', a, docP_), meTokP[1]];
  1084.                         }
  1085.                 }
  1086.         }
  1087.         function parse_mxP_tokP(prec, tokP) /* tokP may be non-atomic */ {
  1088.                 var mx0p = null;
  1089.                 while (true) {
  1090.                         if (! tokP) {
  1091.                                 tokP = scanTokP();
  1092.                                 if (! tokP)     break;
  1093.                         }
  1094.                         var op = tokP[1];       // may be null/undefined
  1095.                         if (! op
  1096.                         || mx0p && (tokP[0] ? ! (M.infix_[op] || M.postfix_[op]) : M.macro1s_[op])) {
  1097.                                 if (! mx0p) {
  1098.                                         mx0p = tokP[0];
  1099.                                         tokP = null;
  1100.                                 } else {
  1101.                                         if (prec >= precAdj_)   break;
  1102.                                         var mxP_tokP = parse_mxP_tokP(precAdj_, tokP), mx1 = mxP_tokP[0];
  1103.                                         mx1 || F.err(err_parse_mxP_tokP_1_);
  1104.                                         var e = M.newMe('mrow', [mx0p, mx1], docP_);
  1105.                                         if (M.hasClass(mx0p, 'ma-repel-adj') || M.hasClass(mx1, 'ma-repel-adj')) {
  1106.                                                 /* setting padding on mx0p or mx1 doesn't work on e.g. <mn> or <mrow>
  1107.                                                         elements in Firefox 3.6.12 */
  1108.                                                 if (! (op && tokP[0] && M.prefix_[op] < 25))
  1109.                                                         $(mx0p).after(M.spaceMe('.17em', docP_));
  1110.                                                 M.addClass(e, 'ma-repel-adj');
  1111.                                         }
  1112.                                         mx0p = e;
  1113.                                         tokP = mxP_tokP[1];
  1114.                                 }
  1115.                         } else {
  1116.                                 var moP = tokP[0];      // could be an embellished <mo> in {↖ }
  1117.                                 if (moP) {
  1118.                                         var precL = M.infix_[op] || M.postfix_[op];
  1119.                                         if (precL && prec >= precL)     break;
  1120.                                         var precROpt = M.infix_[op] || ! (mx0p && M.postfix_[op]) && M.prefix_[op];
  1121.                                         if (! M.MathML && ! mx0p && 290 <= precROpt && precROpt <= 350) {
  1122.                                                 $(moP).addClass('fm-large-op');
  1123.                                                 //+ omit if fm-inline:
  1124.                                                 moP.fmUp = 0.85*1.3 - 0.25;
  1125.                                                 moP.fmDn = 0.35*1.3 + 0.25;
  1126.                                         }
  1127.                                         var meTok_tokP = parseEmbel(tokP), a = [];
  1128.                                         meTok_tokP[0] || F.err(err_parse_mxP_tokP_embel_);
  1129.                                         var extOp = meTok_tokP[0][0];
  1130.                                         tokP = meTok_tokP[1];
  1131.                                         if (mx0p)       a.push(mx0p);
  1132.                                         a.push(extOp);
  1133.                                         if (precROpt) {
  1134.                                                 var mxP_tokP = parse_mxP_tokP(precROpt, tokP);
  1135.                                                 if (mxP_tokP[0])        a.push(mxP_tokP[0]);
  1136.                                                 tokP = mxP_tokP[1];
  1137.                                                 if (precROpt < 25 && ! mx0p) {  // check for fences
  1138.                                                         if (! tokP)     tokP = scanTokP();
  1139.                                                         if (tokP && tokP[1] && tokP[0]
  1140.                                                         && (M.postfix_[tokP[1]] || M.infix_[tokP[1]]) == precROpt) {
  1141.                                                                 // don't parseEmbel() here [after fences]
  1142.                                                                 a.push(tokP[0]);
  1143.                                                                 tokP = null;
  1144.                                                         }
  1145.                                                 }
  1146.                                         }
  1147.                                         if (a.length == 1)      mx0p = a[0];
  1148.                                         else if (op == '/' && mx0p && a.length == 3
  1149.                                         || op == '\u221A' /* &radic; */ && ! mx0p && a.length == 2) {
  1150.                                                 if (op == '\u221A' && M.mtagName(a[0]) == 'msup')
  1151.                                                         mx0p = M.newMe('mroot', [a[1], M.mchilds(a[0])[1]], docP_);
  1152.                                                 else {
  1153.                                                         a.splice(a.length - 2, 1);
  1154.                                                         mx0p = M.newMe(op == '/' ? 'mfrac' : 'msqrt', a, docP_);
  1155.                                                 }
  1156.                                         } else {
  1157.                                                 var e = M.newMe('mrow', a, docP_);
  1158.                                                 if (op == '\u2009' /* &thinsp; */ || (precL || precROpt) >= precAdj_)
  1159.                                                         ;
  1160.                                                 else {
  1161.                                                         var k = '';
  1162.                                                         if (op == '=')  k = 'infix-loose';
  1163.                                                         else if (a.length == 2) {
  1164.                                                                 k = mx0p ? 'postfix' : 'prefix';
  1165.                                                                 if (M.infix_[op])       k += '-tight';
  1166.                                                                 else {
  1167.                                                                         if (/^[∀∃∄∂∇]$/.test(op))     k = 'quantifier';
  1168.                                                                         M.addClass(e, 'ma-repel-adj');
  1169.                                                                 }
  1170.                                                         } else if (mx0p) {      // a.length == 3 && not fences
  1171.                                                                 k = op == ',' || op == ';' ? 'separator' :
  1172.                                                                         precL <= 270 ? 'infix-loose' : 'infix';
  1173.                                                                 if (op == '|' && M.MathML && moP.tagName == 'mo') {
  1174.                                                                         // Firefox work-around
  1175.                                                                         moP.setAttribute('lspace', '.11em');
  1176.                                                                         moP.setAttribute('rspace', '.11em');
  1177.                                                                 }
  1178.                                                         }
  1179.                                                         if (! M.MathML && k && ! moP.style.fontSize)
  1180.                                                                 $(extOp).addClass('fm-'+k);
  1181.                                                 }
  1182.                                                 mx0p = e;
  1183.                                         }
  1184.                                 } else if (op == '}')   break;
  1185.                                 else if (M.macro1s_[op]) {
  1186.                                         ! mx0p || F.err(err_parse_mxP_tokP_macro_);
  1187.                                         var mxP_tokP = M.macro1s_[op]();
  1188.                                         mx0p = mxP_tokP[0];
  1189.                                         tokP = mxP_tokP[1];
  1190.                                 } else {
  1191.                                         embelWs_[op] || F.err(err_parse_mxP_tokP_script_);
  1192.                                         if (prec >= 999)        break;
  1193.                                         var meTok_tokP = parseEmbel(mx0p && [mx0p, null], tokP),
  1194.                                                 meTok = meTok_tokP[0];
  1195.                                         meTok || F.err(err_parse_mxP_tokP_embel_2_);
  1196.                                         tokP = meTok_tokP[1];
  1197.                                         var a = [meTok[0]], opP = meTok[1];
  1198.                                         if (opP) {
  1199.                                                 var precROpt = M.infix_[opP] || M.prefix_[opP];
  1200.                                                 if (precROpt) {
  1201.                                                         var mxP_tokP = parse_mxP_tokP(precROpt, tokP);
  1202.                                                         if (mxP_tokP[0])        a.push(mxP_tokP[0]);
  1203.                                                         tokP = mxP_tokP[1];
  1204.                                                 }
  1205.                                         }
  1206.                                         mx0p = a.length == 1 ? a[0] : M.newMe('mrow', a, docP_);
  1207.                                 }
  1208.                         }
  1209.                 }
  1210.                 return [mx0p, tokP];
  1211.         }
  1212.         M.sToMe = function(g, docP) {
  1213.                 if (! docP)     docP = document;
  1214.                 M.infix_[''] && M.infix_[','] || F.err(err_sToMe_1_);
  1215.                
  1216.                 if (M.MathML == null)   M.MathML = M.canMathML();
  1217.                 M.re_.lastIndex = 0;
  1218.                 s_ = '';
  1219.                 s_or_mx_a_ = Array.isArray(g) ? g : [g];
  1220.                 s_or_mx_i_ = 0;
  1221.                 docP_ = docP;
  1222.                 precAdj_ = M.infix_[''];
  1223.                
  1224.                 var mxP_tokP = parse_mxP_tokP(0);
  1225.                 if (mxP_tokP[1])
  1226.                         throw 'Extra input:  ' + mxP_tokP[1][1] + s_.substring(M.re_.lastIndex) +
  1227.                                 (s_or_mx_i_ < s_or_mx_a_.length ? '...' : '');
  1228.                 if (M.re_.lastIndex < s_.length || s_or_mx_i_ < s_or_mx_a_.length)      F.err(err_sToMe_2_);
  1229.                 return mxP_tokP[0];
  1230.         };
  1231.         M.sToMathE = function(g, blockQ, docP) /* parses strings and includes MathML subexpression
  1232.                         elements into an XML 'math' or HTML 'fmath' element */ {
  1233.                 var res = M.sToMe(g, docP);
  1234.                 if (! (res && F.elem(M.mtagName(res), ['math', 'fmath'])))
  1235.                         res = M.newMe('math', res, docP);
  1236.                 if (typeof g == 'string')       res.setAttribute('alttext', g);
  1237.                 return M.setMathBlockQ(res, blockQ);
  1238.         };
  1239.        
  1240.         /*  Like TeX, we use $ or \( \), and $$ or \[ \], to delimit inline and block ("display")
  1241.                 mathematics, respectively.  Use \$ for an actual $ instead, or \\ for \ if necessary.
  1242.                 */
  1243.         M.$mathQ = true;        // whether $ acts as an (inline) math delimiter
  1244.         M.inline$$Q = false;    /* whether $$ $$ or \[ \] in a <p> or <span> should be wrapped in an
  1245.                 inline-block */
  1246.         M.parseMath = function(nod) {
  1247.                 if (nod.nodeType == 1 /* Element */ && nod.tagName != 'SCRIPT') {
  1248.                         if (nod.tagName.toUpperCase() == 'MATH') {
  1249.                                 var newE = M.eToMathE(nod);
  1250.                                 if (newE != nod)        nod.parentNode.replaceChild(newE, nod);
  1251.                         } else
  1252.                                 for (var p = nod.firstChild; p; ) {
  1253.                                         var restP = p.nextSibling;      // do before splitting 'p'
  1254.                                         M.parseMath(p);
  1255.                                         p = restP;
  1256.                                 }
  1257.                 } else if (nod.nodeType == 3 /* Text */ && /[$\\]/.test(nod.data) /* for speed */) {
  1258.                         var doc = nod.ownerDocument, s = nod.data, a = [], t = '',
  1259.                                 re = /\\([$\\])|\$\$?|\\[([]/g;
  1260.                         while (true) {
  1261.                                 var j = re.lastIndex, m = re.exec(s), k = m ? m.index : s.length;
  1262.                                 if (j < k)      t += s.substring(j, k);
  1263.                                 if (m && m[1])  t += m[1];
  1264.                                 else {
  1265.                                         var i = -1, z;
  1266.                                         if (m) {
  1267.                                                 z = m[0] == '\\(' ? '\\)' : m[0] == '\\[' ? '\\]' : m[0];
  1268.                                                 if (re.lastIndex < s.length && (m[0] != '$' || M.$mathQ)) {
  1269.                                                         i = s.indexOf(z, re.lastIndex);
  1270.                                                         while (i != -1 && s.charAt(i - 1) == '\\')      i = s.indexOf(z, i + 1);
  1271.                                                 }
  1272.                                                 if (i == -1) {
  1273.                                                         t += m[0];
  1274.                                                         continue;
  1275.                                                 }
  1276.                                         }
  1277.                                         if (t) {
  1278.                                                 a.push(doc.createTextNode(t));
  1279.                                                 t = '';
  1280.                                         }
  1281.                                         if (! m)        break;
  1282.                                         var blockQ = m[0] == '$$' || m[0] == '\\[',
  1283.                                                 e = M.sToMathE(s.substring(re.lastIndex, i), blockQ, doc);
  1284.                                         if (blockQ && M.inline$$Q && F.elem(nod.parentNode.nodeName, ['P', 'SPAN']))
  1285.                                         {
  1286.                                                 var wrap$ = $('<div/>', doc).css('display', 'inline-block').append(e);
  1287.                                                 e = wrap$[0];
  1288.                                         }
  1289.                                         a.push(e);
  1290.                                         re.lastIndex = i + z.length;
  1291.                                 }
  1292.                         }
  1293.                         F.iter(function(x) { nod.parentNode.insertBefore(x, nod); }, a);
  1294.                         nod.parentNode.removeChild(nod);
  1295.                 }
  1296.         };
  1297.         M.parseMathQ = true;
  1298.         $(function() {
  1299.                 if (M.MathML == null)   M.MathML = M.canMathML();
  1300.                 if (M.parseMathQ)
  1301.                         try {
  1302.                                 M.parseMath(document.body);
  1303.                         } catch(exc) {
  1304.                                 alert(exc);
  1305.                         }
  1306.         });
  1307.        
  1308.         return M;
  1309. }();
  1310. var M;  if (M === undefined)    M = jqMath;
  1311.