Home | History | Annotate | Download | only in common
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview A semantic tree for MathML expressions.
      7  *
      8  * This file contains functionality to compute a semantic interpretation from a
      9  * given MathML expression. This is a very heuristic approach that assumes a
     10  * fairly simple default semantic which is suitable for K-12 and simple UG
     11  * mathematics.
     12  *
     13  */
     14 
     15 goog.provide('cvox.SemanticTree');
     16 goog.provide('cvox.SemanticTree.Node');
     17 
     18 goog.require('cvox.DomUtil');
     19 goog.require('cvox.SemanticAttr');
     20 goog.require('cvox.SemanticUtil');
     21 
     22 
     23 /**
     24  * Create an initial semantic tree.
     25  * @param {!Element} mml The original MathML node.
     26  * @constructor
     27  */
     28 cvox.SemanticTree = function(mml) {
     29   /** ID counter.
     30    * @type {number}
     31    * @private
     32    */
     33   this.idCounter_ = 0;
     34 
     35   /** Original MathML tree.
     36    * @type {Node}
     37    */
     38   this.mathml = mml;
     39 
     40   /** @type {cvox.SemanticTree.Node} */
     41   this.root = this.parseMathml_(mml);
     42 };
     43 
     44 
     45 /**
     46  * @param {number} id Node id.
     47  * @constructor
     48  */
     49 cvox.SemanticTree.Node = function(id) {
     50   /** @type {number} */
     51   this.id = id;
     52 
     53   /** @type {Array.<Element>} */
     54   this.mathml = [];
     55 
     56   /** @type {cvox.SemanticTree.Node} */
     57   this.parent = null;
     58 
     59   /** @type {cvox.SemanticAttr.Type} */
     60   this.type = cvox.SemanticAttr.Type.UNKNOWN;
     61 
     62   /** @type {cvox.SemanticAttr.Role} */
     63   this.role = cvox.SemanticAttr.Role.UNKNOWN;
     64 
     65   /** @type {cvox.SemanticAttr.Font} */
     66   this.font = cvox.SemanticAttr.Font.UNKNOWN;
     67 
     68   /** @type {!Array.<cvox.SemanticTree.Node>} */
     69   this.childNodes = [];
     70 
     71   /** @type {string} */
     72   this.textContent = '';
     73 
     74   /** Branch nodes can store additional nodes that can be useful.
     75    * E.g. a node of type FENCED can have the opening and closing fences here.
     76    * @type {!Array.<cvox.SemanticTree.Node>}
     77    */
     78   this.contentNodes = [];
     79 };
     80 
     81 
     82 /**
     83  * Retrieve all subnodes (including the node itself) that satisfy a given
     84  * predicate.
     85  * @param {function(cvox.SemanticTree.Node): boolean} pred The predicate.
     86  * @return {!Array.<cvox.SemanticTree.Node>} The nodes in the tree for which the
     87  *     predicate holds.
     88  */
     89 cvox.SemanticTree.Node.prototype.querySelectorAll = function(pred) {
     90   var result = [];
     91   for (var i = 0, child; child = this.childNodes[i]; i++) {
     92     result = result.concat(child.querySelectorAll(pred));
     93   }
     94   if (pred(this)) {
     95     result.unshift(this);
     96   }
     97   return result;
     98 };
     99 
    100 
    101  /**
    102   * Returns an XML representation of the tree.
    103   * @param {boolean=} brief If set attributes are omitted.
    104   * @return {Node} The XML representation of the tree.
    105   */
    106  cvox.SemanticTree.prototype.xml = function(brief) {
    107    var dp = new DOMParser();
    108    var xml = dp.parseFromString('<stree></stree>', 'text/xml');
    109 
    110    var xmlRoot = this.root.xml(xml, brief);
    111    xml.childNodes[0].appendChild(xmlRoot);
    112 
    113    return xml.childNodes[0];
    114  };
    115 
    116 
    117  /**
    118   * An XML tree representation of the current node.
    119   * @param {Document} xml The XML document.
    120   * @param {boolean=} brief If set attributes are omitted.
    121   * @return {Node} The XML representation of the node.
    122   */
    123  cvox.SemanticTree.Node.prototype.xml = function(xml, brief) {
    124    /**
    125     * Translates a list of nodes into XML representation.
    126     * @param {string} tag Name of the enclosing tag.
    127     * @param {!Array.<!cvox.SemanticTree.Node>} nodes A list of nodes.
    128     * @return {Node} An XML representation of the node list.
    129     */
    130    var xmlNodeList = function(tag, nodes) {
    131      var xmlNodes = nodes.map(function(x) {return x.xml(xml, brief);});
    132      var tagNode = xml.createElement(tag);
    133      for (var i = 0, child; child = xmlNodes[i]; i++) {
    134        tagNode.appendChild(child);
    135      }
    136      return tagNode;
    137    };
    138    var node = xml.createElement(this.type);
    139    if (!brief) {
    140      this.xmlAttributes_(node);
    141    }
    142    node.textContent = this.textContent;
    143    if (this.contentNodes.length > 0) {
    144      node.appendChild(xmlNodeList('content', this.contentNodes));
    145    }
    146    if (this.childNodes.length > 0) {
    147      node.appendChild(xmlNodeList('children', this.childNodes));
    148    }
    149    return node;
    150  };
    151 
    152 
    153 /**
    154   * Serializes the XML representation of the tree.
    155   * @param {boolean=} brief If set attributes are omitted.
    156  * @return {string} Serialized string.
    157  */
    158 cvox.SemanticTree.prototype.toString = function(brief) {
    159   var xmls = new XMLSerializer();
    160   return xmls.serializeToString(this.xml(brief));
    161 };
    162 
    163 
    164 /**
    165  * Pretty print the XML representation of the tree.
    166  * @param {boolean=} brief If set attributes are omitted.
    167  * @return {string} The formatted string.
    168  */
    169 cvox.SemanticTree.prototype.formatXml = function(brief) {
    170   var xml = this.toString(brief);
    171   return cvox.SemanticTree.formatXml(xml);
    172 };
    173 
    174 
    175 /**
    176  * Pretty prints an XML representation.
    177  * @param {string} xml The serialised XML string.
    178  * @return {string} The formatted string.
    179  */
    180 cvox.SemanticTree.formatXml = function(xml) {
    181   var reg = /(>)(<)(\/*)/g;
    182   xml = xml.replace(reg, '$1\r\n$2$3');
    183   reg = /(>)(.+)(<c)/g;
    184   xml = xml.replace(reg, '$1\r\n$2\r\n$3');
    185   var formatted = '';
    186   var padding = '';
    187   xml.split('\r\n')
    188       .forEach(function(node) {
    189                  if (node.match(/.+<\/\w[^>]*>$/)) {
    190                    // Node with content.
    191                    formatted += padding + node + '\r\n';
    192                  } else if (node.match(/^<\/\w/)) {
    193                    if (padding) {
    194                      // Closing tag
    195                      padding = padding.slice(2);
    196                      formatted += padding + node + '\r\n';
    197                    }
    198                  } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
    199                    // Opening tag
    200                    formatted += padding + node + '\r\n';
    201                    padding += '  ';
    202                  } else {
    203                    // Empty tag
    204                    formatted += padding + node + '\r\n';
    205                  }
    206                });
    207   return formatted;
    208 };
    209 
    210 
    211 /**
    212  * Serializes the XML representation of a node.
    213  * @param {boolean=} brief If attributes are to be omitted.
    214  * @return {string} Serialized string.
    215  */
    216 cvox.SemanticTree.Node.prototype.toString = function(brief) {
    217   var xmls = new XMLSerializer();
    218   var dp = new DOMParser();
    219   var xml = dp.parseFromString('', 'text/xml');
    220   return xmls.serializeToString(this.xml(xml, brief));
    221 };
    222 
    223 
    224 /**
    225  * Adds attributes to the XML representation of the current node.
    226  * @param {Node} node The XML node.
    227  * @private
    228  */
    229 cvox.SemanticTree.Node.prototype.xmlAttributes_ = function(node) {
    230   node.setAttribute('role', this.role);
    231   if (this.font != cvox.SemanticAttr.Font.UNKNOWN) {
    232     node.setAttribute('font', this.font);
    233   }
    234   node.setAttribute('id', this.id);
    235 };
    236 
    237 
    238 /** Creates a new node object.
    239  * @return {cvox.SemanticTree.Node} The newly created node.
    240  * @private
    241  */
    242 cvox.SemanticTree.prototype.createNode_ = function() {
    243   return new cvox.SemanticTree.Node(this.idCounter_++);
    244 };
    245 
    246 
    247 /**
    248  * Replaces a node in the tree. Updates the root node if necessary.
    249  * @param {!cvox.SemanticTree.Node} oldNode The node to be replaced.
    250  * @param {!cvox.SemanticTree.Node} newNode The new node.
    251  * @private
    252  */
    253 cvox.SemanticTree.prototype.replaceNode_ = function(oldNode, newNode) {
    254   var parent = oldNode.parent;
    255   if (!parent) {
    256     this.root = newNode;
    257     return;
    258   }
    259   parent.replaceChild_(oldNode, newNode);
    260 };
    261 
    262 
    263 /**
    264  * Updates the content of the node thereby possibly changing type and role.
    265  * @param {string} content The new content string.
    266  * @private
    267  */
    268 cvox.SemanticTree.Node.prototype.updateContent_ = function(content) {
    269   // Remove superfluous whitespace!
    270   content = content.trim();
    271   if (this.textContent == content) {
    272     return;
    273   }
    274   var meaning = cvox.SemanticAttr.lookupMeaning(content);
    275   this.textContent = content;
    276   this.role = meaning.role;
    277   this.type = meaning.type;
    278   this.font = meaning.font;
    279 };
    280 
    281 
    282 /**
    283  * Adds MathML nodes to the node's store of MathML nodes if necessary only, as
    284  * we can not necessarily assume that the MathML of the content nodes and
    285  * children are all disjoint.
    286  * @param {Array.<Node>} mmlNodes List of MathML nodes.
    287  * @private
    288  */
    289 cvox.SemanticTree.Node.prototype.addMathmlNodes_ = function(mmlNodes) {
    290   for (var i = 0, mml; mml = mmlNodes[i]; i++) {
    291     if (this.mathml.indexOf(mml) == -1) {
    292       this.mathml.push(mml);
    293     }
    294   }
    295 };
    296 
    297 
    298 /**
    299  * Removes MathML nodes from the node's store of MathML nodes.
    300  * @param {Array.<Node>} mmlNodes List of MathML nodes.
    301  * @private
    302  */
    303 cvox.SemanticTree.Node.prototype.removeMathmlNodes_ = function(mmlNodes) {
    304   var mmlList = this.mathml;
    305   for (var i = 0, mml; mml = mmlNodes[i]; i++) {
    306     var index = mmlList.indexOf(mml);
    307     if (index != -1) {
    308       mmlList.splice(index, 1);
    309     }
    310   }
    311   this.mathml = mmlList;
    312 };
    313 
    314 
    315 /**
    316  * Appends a child to the node.
    317  * @param {cvox.SemanticTree.Node} child The new child.
    318  * @private
    319  */
    320 cvox.SemanticTree.Node.prototype.appendChild_ = function(child) {
    321   this.childNodes.push(child);
    322   this.addMathmlNodes_(child.mathml);
    323   child.parent = this;
    324 };
    325 
    326 
    327 /**
    328  * Replaces a child node of the node.
    329  * @param {!cvox.SemanticTree.Node} oldNode The node to be replaced.
    330  * @param {!cvox.SemanticTree.Node} newNode The new node.
    331  * @private
    332  */
    333 cvox.SemanticTree.Node.prototype.replaceChild_ = function(oldNode, newNode) {
    334   var index = this.childNodes.indexOf(oldNode);
    335   if (index == -1) {
    336     return;
    337   }
    338   newNode.parent = this;
    339   oldNode.parent = null;
    340   this.childNodes[index] = newNode;
    341   // To not mess up the order of MathML elements more than necessary, we only
    342   // remove and add difference lists. The hope is that we might end up with
    343   // little change.
    344   var removeMathml = oldNode.mathml.filter(
    345       function(x) {return newNode.mathml.indexOf(x) == -1;});
    346   var addMathml = newNode.mathml.filter(
    347       function(x) {return oldNode.mathml.indexOf(x) == -1;});
    348   this.removeMathmlNodes_(removeMathml);
    349   this.addMathmlNodes_(addMathml);
    350 };
    351 
    352 
    353 /**
    354  * Appends a content node to the node.
    355  * @param {cvox.SemanticTree.Node} node The new content node.
    356  * @private
    357  */
    358 cvox.SemanticTree.Node.prototype.appendContentNode_ = function(node) {
    359   if (node) {
    360     this.contentNodes.push(node);
    361     this.addMathmlNodes_(node.mathml);
    362     node.parent = this;
    363   }
    364 };
    365 
    366 
    367 /**
    368  * Removes a content node from the node.
    369  * @param {cvox.SemanticTree.Node} node The content node to be removed.
    370  * @private
    371  */
    372 cvox.SemanticTree.Node.prototype.removeContentNode_ = function(node) {
    373   if (node) {
    374     var index = this.contentNodes.indexOf(node);
    375     if (index != -1) {
    376       this.contentNodes.splice(index, 1);
    377     }
    378   }
    379 };
    380 
    381 
    382 /**
    383  * This is the main function that creates the semantic tree by recursively
    384  * parsing the initial MathML tree and bottom up assembling the tree.
    385  * @param {!Element} mml The MathML tree.
    386  * @return {!cvox.SemanticTree.Node} The root of the new tree.
    387  * @private
    388  */
    389 cvox.SemanticTree.prototype.parseMathml_ = function(mml) {
    390   var children = cvox.DomUtil.toArray(mml.children);
    391   switch (cvox.SemanticUtil.tagName(mml)) {
    392     case 'MATH':
    393     case 'MROW':
    394     case 'MPADDED':
    395     case 'MSTYLE':
    396       children = cvox.SemanticUtil.purgeNodes(children);
    397       // Single child node, i.e. the row is meaningless.
    398       if (children.length == 1) {
    399         return this.parseMathml_(/** @type {!Element} */(children[0]));
    400       }
    401       // Case of a 'meaningful' row, even if they are empty.
    402       return this.processRow_(this.parseMathmlChildren_(children));
    403     break;
    404     case 'MFRAC':
    405       var newNode = this.makeBranchNode_(
    406           cvox.SemanticAttr.Type.FRACTION,
    407           [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
    408           []);
    409       newNode.role = cvox.SemanticAttr.Role.DIVISION;
    410       return newNode;
    411       break;
    412     case 'MSUB':
    413     case 'MSUP':
    414     case 'MSUBSUP':
    415     case 'MOVER':
    416     case 'MUNDER':
    417     case 'MUNDEROVER':
    418       return this.makeLimitNode_(cvox.SemanticUtil.tagName(mml),
    419                                  this.parseMathmlChildren_(children));
    420       break;
    421     case 'MROOT':
    422       return this.makeBranchNode_(
    423           cvox.SemanticAttr.Type.ROOT,
    424           [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
    425           []);
    426       break;
    427     case 'MSQRT':
    428       children = this.parseMathmlChildren_(
    429           cvox.SemanticUtil.purgeNodes(children));
    430       return this.makeBranchNode_(
    431           cvox.SemanticAttr.Type.SQRT, [this.processRow_(children)], []);
    432       break;
    433     case 'MTABLE':
    434       newNode = this.makeBranchNode_(
    435           cvox.SemanticAttr.Type.TABLE,
    436           this.parseMathmlChildren_(children), []);
    437       if (cvox.SemanticTree.tableIsMultiline_(newNode)) {
    438         this.tableToMultiline_(newNode);
    439         }
    440       return newNode;
    441       break;
    442     case 'MTR':
    443       newNode = this.makeBranchNode_(
    444           cvox.SemanticAttr.Type.ROW,
    445           this.parseMathmlChildren_(children), []);
    446       newNode.role = cvox.SemanticAttr.Role.TABLE;
    447       return newNode;
    448       break;
    449     case 'MTD':
    450       children = this.parseMathmlChildren_(
    451           cvox.SemanticUtil.purgeNodes(children));
    452       newNode = this.makeBranchNode_(
    453           cvox.SemanticAttr.Type.CELL, [this.processRow_(children)], []);
    454       newNode.role = cvox.SemanticAttr.Role.TABLE;
    455       return newNode;
    456       break;
    457     case 'MTEXT':
    458       var leaf = this.makeLeafNode_(mml);
    459       leaf.type = cvox.SemanticAttr.Type.TEXT;
    460       return leaf;
    461       break;
    462     // TODO (sorge) Role and font of multi-character and digits unicode strings.
    463     // TODO (sorge) Reclassify wrongly tagged numbers or identifiers.
    464     // TODO (sorge) Put this all in a single clean reclassification method.
    465     case 'MI':
    466       leaf = this.makeLeafNode_(mml);
    467       if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
    468         leaf.type = cvox.SemanticAttr.Type.IDENTIFIER;
    469       }
    470       return leaf;
    471       break;
    472     case 'MN':
    473       leaf = this.makeLeafNode_(mml);
    474       if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
    475         leaf.type = cvox.SemanticAttr.Type.NUMBER;
    476       }
    477       return leaf;
    478       break;
    479     case 'MO':
    480       leaf = this.makeLeafNode_(mml);
    481       if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
    482         leaf.type = cvox.SemanticAttr.Type.OPERATOR;
    483       }
    484       return leaf;
    485       break;
    486     // TODO (sorge) Do something useful with error and phantom symbols.
    487     default:
    488     // Ordinarilly at this point we should not get any other tag.
    489       return this.makeUnprocessed_(mml);
    490   }
    491 };
    492 
    493 
    494 /**
    495  * Parse a list of MathML nodes into the semantic tree.
    496  * @param {Array.<Element>} mmls A list of MathML nodes.
    497  * @return {!Array.<cvox.SemanticTree.Node>} The list of resulting semantic
    498  *     node.
    499  * @private
    500  */
    501 cvox.SemanticTree.prototype.parseMathmlChildren_ = function(mmls) {
    502   var result = [];
    503   for (var i = 0, mml; mml = mmls[i]; i++) {
    504     result.push(this.parseMathml_(mml));
    505   }
    506   return result;
    507 };
    508 
    509 /**
    510  * Create a node that is to be processed at a later point in time.
    511  * @param {Node} mml The MathML tree.
    512  * @return {!cvox.SemanticTree.Node} The new node.
    513  * @private
    514  */
    515 cvox.SemanticTree.prototype.makeUnprocessed_ = function(mml) {
    516   var node = this.createNode_();
    517   node.mathml = [mml];
    518   return node;
    519 };
    520 
    521 
    522 /**
    523  * Create an empty leaf node.
    524  * @return {!cvox.SemanticTree.Node} The new node.
    525  * @private
    526  */
    527 cvox.SemanticTree.prototype.makeEmptyNode_ = function() {
    528   var node = this.createNode_();
    529   node.type = cvox.SemanticAttr.Type.EMPTY;
    530   return node;
    531 };
    532 
    533 
    534 /**
    535  * Create a leaf node.
    536  * @param {Node} mml The MathML tree.
    537  * @return {!cvox.SemanticTree.Node} The new node.
    538  * @private
    539  */
    540 cvox.SemanticTree.prototype.makeLeafNode_ = function(mml) {
    541   var node = this.createNode_();
    542   node.mathml = [mml];
    543   node.updateContent_(mml.textContent);
    544   node.font = mml.getAttribute('mathvariant') || node.font;
    545   return node;
    546 };
    547 
    548 
    549 /**
    550  * Create a branching node.
    551  * @param {!cvox.SemanticAttr.Type} type The type of the node.
    552  * @param {!Array.<cvox.SemanticTree.Node>} children The child nodes.
    553  * @param {!Array.<cvox.SemanticTree.Node>} contentNodes The content Nodes.
    554  * @param {string=} content Content string if there is any.
    555  * @return {!cvox.SemanticTree.Node} The new node.
    556  * @private
    557  */
    558 cvox.SemanticTree.prototype.makeBranchNode_ = function(
    559     type, children, contentNodes, content) {
    560   var node = this.createNode_();
    561   if (content) {
    562     node.updateContent_(content);
    563   }
    564   node.type = type;
    565   node.childNodes = children;
    566   node.contentNodes = contentNodes;
    567   children.concat(contentNodes)
    568       .forEach(
    569           function(x) {
    570             x.parent = node;
    571             node.addMathmlNodes_(x.mathml);
    572           });
    573   return node;
    574 };
    575 
    576 
    577 /**
    578  * Create a branching node for an implicit operation, currently assumed to
    579  * be of multiplicative type.
    580  * @param {!Array.<!cvox.SemanticTree.Node>} nodes The operands.
    581  * @return {!cvox.SemanticTree.Node} The new branch node.
    582  * @private
    583  */
    584 cvox.SemanticTree.prototype.makeImplicitNode_ = function(nodes) {
    585   if (nodes.length == 1) {
    586     return nodes[0];
    587     }
    588   var operator = this.createNode_();
    589   // For now we assume this is a multiplication using invisible times.
    590   operator.updateContent_(cvox.SemanticAttr.invisibleTimes());
    591   var newNode = this.makeInfixNode_(nodes, operator);
    592   newNode.role = cvox.SemanticAttr.Role.IMPLICIT;
    593   return newNode;
    594 };
    595 
    596 
    597 /**
    598  * Create a branching node for an infix operation.
    599  * @param {!Array.<cvox.SemanticTree.Node>} children The operands.
    600  * @param {!cvox.SemanticTree.Node} opNode The operator.
    601  * @return {!cvox.SemanticTree.Node} The new branch node.
    602  * @private
    603  */
    604 cvox.SemanticTree.prototype.makeInfixNode_ = function(children, opNode) {
    605   return this.makeBranchNode_(
    606       cvox.SemanticAttr.Type.INFIXOP, children, [opNode], opNode.textContent);
    607 };
    608 
    609 
    610 /**
    611  * Creates a node of the specified type by collapsing the given node list into
    612  * one content (thereby concatenating the content of each node into a single
    613  * content string) with the inner node as a child.
    614  * @param {!cvox.SemanticTree.Node} inner The inner node.
    615  * @param {!Array.<cvox.SemanticTree.Node>} nodeList List of nodes.
    616  * @param {!cvox.SemanticAttr.Type} type The new type of the node.
    617  * @return {!cvox.SemanticTree.Node} The new branch node.
    618  * @private
    619  */
    620 cvox.SemanticTree.prototype.makeConcatNode_ = function(inner, nodeList, type) {
    621   if (nodeList.length == 0) {
    622     return inner;
    623   }
    624   var content = nodeList.map(function(x) {return x.textContent;}).join(' ');
    625   var newNode = this.makeBranchNode_(type, [inner], nodeList, content);
    626   if (nodeList.length > 0) {
    627     newNode.role = cvox.SemanticAttr.Role.MULTIOP;
    628   }
    629   return newNode;
    630 };
    631 
    632 
    633 /**
    634  * Wraps a node into prefix operators.
    635  * Example: + - a becomes (+ (- (a)))
    636  * Input: a  [+, -] ->  Output: content: '+ -', child: a
    637  * @param {!cvox.SemanticTree.Node} node The inner node.
    638  * @param {!Array.<cvox.SemanticTree.Node>} prefixes Prefix operators
    639  * from the outermost to the innermost.
    640  * @return {!cvox.SemanticTree.Node} The new branch node.
    641  * @private
    642  */
    643 cvox.SemanticTree.prototype.makePrefixNode_ = function(node, prefixes) {
    644   var negatives = cvox.SemanticTree.partitionNodes_(
    645       prefixes, cvox.SemanticTree.attrPred_('role', 'SUBTRACTION'));
    646   var newNode = this.makeConcatNode_(
    647       node, negatives.comp.pop(), cvox.SemanticAttr.Type.PREFIXOP);
    648 
    649   while (negatives.rel.length > 0) {
    650     newNode = this.makeConcatNode_(
    651         newNode, [negatives.rel.pop()], cvox.SemanticAttr.Type.PREFIXOP);
    652     newNode.role = cvox.SemanticAttr.Role.NEGATIVE;
    653     newNode = this.makeConcatNode_(
    654         newNode, negatives.comp.pop(), cvox.SemanticAttr.Type.PREFIXOP);
    655   }
    656   return newNode;
    657 };
    658 
    659 
    660 /**
    661  * Wraps a node into postfix operators.
    662  * Example: a - + becomes (((a) -) +)
    663  * Input: a  [-, +] ->  Output: content: '- +', child: a
    664  * @param {!cvox.SemanticTree.Node} node The inner node.
    665  * @param {!Array.<cvox.SemanticTree.Node>} postfixes Postfix operators from
    666  * innermost to outermost.
    667  * @return {!cvox.SemanticTree.Node} The new branch node.
    668  * @private
    669  */
    670 cvox.SemanticTree.prototype.makePostfixNode_ = function(node, postfixes) {
    671   return this.makeConcatNode_(
    672       node, postfixes, cvox.SemanticAttr.Type.POSTFIXOP);
    673 };
    674 
    675 
    676 // TODO (sorge) Separate out interspersed text before the relations in row
    677 // heuristic otherwise we get them as implicit operations!
    678 // Currently we handle that later in the rules, which is rather messy.
    679 /**
    680  * Processes a list of nodes, combining expressions by delimiters, tables,
    681  * punctuation sequences, function/big operator/integral applications to
    682  * generate a syntax tree with relation and operator precedence.
    683  *
    684  * This is the main heuristic to rewrite a flat row of terms into a meaningful
    685  * term tree.
    686  * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
    687  * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
    688  * @private
    689  */
    690 cvox.SemanticTree.prototype.processRow_ = function(nodes) {
    691   if (nodes.length == 0) {
    692     return this.makeEmptyNode_();
    693   }
    694   nodes = this.getFencesInRow_(nodes);
    695   nodes = this.processTablesInRow_(nodes);
    696   nodes = this.getPunctuationInRow_(nodes);
    697   nodes = this.getFunctionsInRow_(nodes);
    698   return this.processRelationsInRow_(nodes);
    699 };
    700 
    701 
    702 /**
    703  * Constructs a syntax tree with relation and operator precedence from a list
    704  * of nodes.
    705  * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
    706  * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
    707  * @private
    708  */
    709 cvox.SemanticTree.prototype.processRelationsInRow_ = function(nodes) {
    710   var partition = cvox.SemanticTree.partitionNodes_(
    711       nodes, cvox.SemanticTree.attrPred_('type', 'RELATION'));
    712   var firstRel = partition.rel[0];
    713 
    714   if (!firstRel) {
    715     return this.processOperationsInRow_(nodes);
    716   }
    717   if (nodes.length == 1) {
    718     return nodes[0];
    719   }
    720   var children = partition.comp.map(
    721       goog.bind(this.processOperationsInRow_, this));
    722   if (partition.rel.every(
    723          function(x) {return x.textContent == firstRel.textContent;})) {
    724     return this.makeBranchNode_(
    725         cvox.SemanticAttr.Type.RELSEQ, children, partition.rel,
    726         firstRel.textContent);
    727   }
    728   return this.makeBranchNode_(
    729       cvox.SemanticAttr.Type.MULTIREL, children, partition.rel);
    730 };
    731 
    732 
    733 /**
    734  * Constructs a syntax tree with operator precedence from a list nodes.
    735  * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
    736  * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
    737  * @private
    738  */
    739 cvox.SemanticTree.prototype.processOperationsInRow_ = function(nodes) {
    740   if (nodes.length == 0) {
    741     return this.makeEmptyNode_();
    742   }
    743   if (nodes.length == 1) {
    744     return nodes[0];
    745   }
    746 
    747   var prefix = [];
    748   while (nodes.length > 0 &&
    749       nodes[0].type == cvox.SemanticAttr.Type.OPERATOR) {
    750     prefix.push(nodes.shift());
    751   }
    752   // Pathological case: only operators in row.
    753   if (nodes.length == 0) {
    754     return this.makePrefixNode_(prefix.pop(), prefix);
    755   }
    756   if (nodes.length == 1) {
    757     return this.makePrefixNode_(nodes[0], prefix);
    758   }
    759 
    760   var split = cvox.SemanticTree.sliceNodes_(
    761       nodes, cvox.SemanticTree.attrPred_('type', 'OPERATOR'));
    762   // At this point, we know that split.head is not empty!
    763   var node = this.makePrefixNode_(
    764       this.makeImplicitNode_(
    765           /** @type {!Array.<!cvox.SemanticTree.Node>} */ (split.head)),
    766       prefix);
    767   if (!split.div) {
    768     return node;
    769   }
    770   return this.makeOperationsTree_(split.tail, node, split.div);
    771 };
    772 
    773 
    774 /**
    775  * Recursively constructs syntax tree with operator precedence from a list nodes
    776  * given a initial root node.
    777  * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
    778  * @param {!cvox.SemanticTree.Node} root Initial tree.
    779  * @param {!cvox.SemanticTree.Node} lastop Last operator that has not been
    780  * processed yet.
    781  * @param {Array.<cvox.SemanticTree.Node>=} prefixes Operator nodes that will
    782  * become prefix operation (or postfix in case they come after last operand).
    783  * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
    784  * @private
    785  */
    786 cvox.SemanticTree.prototype.makeOperationsTree_ = function(
    787     nodes, root, lastop, prefixes) {
    788   prefixes = prefixes || [];
    789 
    790   if (nodes.length == 0) {
    791     // Left over prefixes become postfixes.
    792     prefixes.unshift(lastop);
    793     if (root.type == cvox.SemanticAttr.Type.INFIXOP) {
    794       // We assume prefixes bind stronger than postfixes.
    795       var node = this.makePostfixNode_(
    796           // Here we know that the childNodes are not empty!
    797           /** @type {!cvox.SemanticTree.Node} */ (root.childNodes.pop()),
    798           prefixes);
    799       root.appendChild_(node);
    800       return root;
    801     }
    802     return this.makePostfixNode_(root, prefixes);
    803   }
    804 
    805   var split = cvox.SemanticTree.sliceNodes_(
    806       nodes, cvox.SemanticTree.attrPred_('type', 'OPERATOR'));
    807 
    808   if (split.head.length == 0) {
    809     prefixes.push(split.div);
    810     return this.makeOperationsTree_(split.tail, root, lastop, prefixes);
    811   }
    812 
    813   var node = this.makePrefixNode_(
    814       this.makeImplicitNode_(split.head), prefixes);
    815   var newNode = this.appendOperand_(root, lastop, node);
    816   if (!split.div) {
    817     return newNode;
    818   }
    819 
    820   return this.makeOperationsTree_(split.tail, newNode, split.div, []);
    821 };
    822 
    823 // TODO (sorge) The following four functions could be combined into
    824 // a single one. Currently it is clearer the way it is, though.
    825 /**
    826  * Appends an operand at the right place in an operator tree.
    827  * @param {!cvox.SemanticTree.Node} root The operator tree.
    828  * @param {!cvox.SemanticTree.Node} op The operator node.
    829  * @param {!cvox.SemanticTree.Node} node The node to be added.
    830  * @return {!cvox.SemanticTree.Node} The modified root node.
    831  * @private
    832  */
    833 cvox.SemanticTree.prototype.appendOperand_ = function(root, op, node) {
    834   // In general our operator tree will have the form that additions and
    835   // subtractions are stacked, while multiplications are subordinate.
    836   if (root.type != cvox.SemanticAttr.Type.INFIXOP) {
    837     return this.makeInfixNode_([root, node], op);
    838   }
    839   if (this.appendExistingOperator_(root, op, node)) {
    840     return root;
    841   }
    842   return op.role == cvox.SemanticAttr.Role.MULTIPLICATION ?
    843       this.appendMultiplicativeOp_(root, op, node) :
    844           this.appendAdditiveOp_(root, op, node);
    845 };
    846 
    847 
    848 /**
    849  * Appends a multiplicative operator and operand.
    850  * @param {!cvox.SemanticTree.Node} root The root node.
    851  * @param {!cvox.SemanticTree.Node} op The operator node.
    852  * @param {!cvox.SemanticTree.Node} node The operand node to be added.
    853  * @return {!cvox.SemanticTree.Node} The modified root node.
    854  * @private
    855  */
    856 cvox.SemanticTree.prototype.appendMultiplicativeOp_ = function(root, op, node) {
    857   var lastRoot = root;
    858   var lastChild = root.childNodes[root.childNodes.length - 1];
    859   while (lastChild && lastChild.type == cvox.SemanticAttr.Type.INFIXOP) {
    860     lastRoot = lastChild;
    861     lastChild = lastRoot.childNodes[root.childNodes.length - 1];
    862   }
    863   var newNode = this.makeInfixNode_([lastRoot.childNodes.pop(), node], op);
    864   lastRoot.appendChild_(newNode);
    865   return root;
    866 };
    867 
    868 
    869 /**
    870  * Appends an additive/substractive operator and operand.
    871  * @param {!cvox.SemanticTree.Node} root The old root node.
    872  * @param {!cvox.SemanticTree.Node} op The operator node.
    873  * @param {!cvox.SemanticTree.Node} node The operand node to be added.
    874  * @return {!cvox.SemanticTree.Node} The new root node.
    875  * @private
    876  */
    877 cvox.SemanticTree.prototype.appendAdditiveOp_ = function(root, op, node) {
    878   return this.makeInfixNode_([root, node], op);
    879 };
    880 
    881 
    882 /**
    883  * Adds an operand to an operator node if it is the continuation of an existing
    884  * operation.
    885  * @param {!cvox.SemanticTree.Node} root The root node.
    886  * @param {!cvox.SemanticTree.Node} op The operator node.
    887  * @param {!cvox.SemanticTree.Node} node The operand node to be added.
    888  * @return {boolean} True if operator was successfully appended.
    889  * @private
    890  */
    891 cvox.SemanticTree.prototype.appendExistingOperator_ = function(root, op, node) {
    892   if (!root || root.type != cvox.SemanticAttr.Type.INFIXOP) {
    893     return false;
    894   }
    895   if (root.textContent == op.textContent) {
    896     root.appendContentNode_(op);
    897     root.appendChild_(node);
    898     return true;
    899   }
    900   this.appendExistingOperator_(
    901       // Again, if this is an INFIXOP node, we know it has a child!
    902       /** @type {!cvox.SemanticTree.Node} */
    903       (root.childNodes[root.childNodes.length - 1]),
    904       op, node);
    905 };
    906 
    907 
    908 // TODO (sorge) The following procedure needs a rational reconstruction. It
    909 // contains a number of similar cases which should be combined.
    910 /**
    911  * Combines delimited expressions in a list of nodes.
    912  *
    913  * The basic idea of the heuristic is as follows:
    914  * 1. Opening and closing delimiters are matched regardless of the actual shape
    915  *    of the fence. These are turned into fenced nodes.
    916  * 2. Neutral fences are matched only with neutral fences of the same shape.
    917  * 3. For a collection of unmatched neutral fences we try to get a maximum
    918  *    number of matching fences. E.g. || a|b || would be turned into a fenced
    919  *    node with fences || and content a|b.
    920  * 4. Any remaining unmatched delimiters are turned into punctuation nodes.
    921  * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
    922  * @return {!Array.<!cvox.SemanticTree.Node>} The new list of nodes.
    923  * @private
    924  */
    925 cvox.SemanticTree.prototype.getFencesInRow_ = function(nodes) {
    926   var partition = cvox.SemanticTree.partitionNodes_(
    927       nodes, cvox.SemanticTree.attrPred_('type', 'FENCE'));
    928   var felem = partition.comp.shift();
    929   return this.processFences_(partition.rel, partition.comp, [], [felem]);
    930 };
    931 
    932 
    933 /**
    934  * Recursively processes a list of nodes and combines all the fenced expressions
    935  * into single nodes. It also processes singular fences, building expressions
    936  * that are only fenced left or right.
    937  * @param {!Array.<cvox.SemanticTree.Node>} fences FIFO queue of fence nodes.
    938  * @param {!Array.<Array.<cvox.SemanticTree.Node>>} content FIFO queue content
    939  *     between fences.
    940  * @param {!Array.<cvox.SemanticTree.Node>} openStack LIFO stack of open fences.
    941  * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} contentStack LIFO stack of
    942  *     content between fences yet to be processed.
    943  * @return {!Array.<cvox.SemanticTree.Node>} A list of nodes with all fenced
    944  *     expressions processed.
    945  * @private
    946  */
    947 cvox.SemanticTree.prototype.processFences_ = function(
    948   fences, content, openStack, contentStack) {
    949   // Base case 1: Everything is used up.
    950   if (fences.length == 0 && openStack.length == 0) {
    951     return contentStack[0];
    952     }
    953   var openPred = cvox.SemanticTree.attrPred_('role', 'OPEN');
    954   // Base case 2: Only open and neutral fences are left on the stack.
    955   if (fences.length == 0) {
    956     // Basic idea:
    957     // - make punctuation nodes from open fences
    958     // - combine as many neutral fences as possible, if the are not separated by
    959     //   open fences.
    960     // The idea is to allow for things like case statements etc. and not bury
    961     // them inside a neutral fenced expression.
    962     //
    963     // 0. We process the list from left to right. Hence the first element on the
    964     //    content stack are actually left most elements in the expression.
    965     // 1. Slice at open fence.
    966     // 2. On tail optimize for neutral fences.
    967     // 3. Repeat until fence stack is exhausted.
    968     // Push rightmost elements onto the result.
    969     var result = contentStack.shift();
    970     while (openStack.length > 0) {
    971       if (openPred(openStack[0])) {
    972         var firstOpen = openStack.shift();
    973         cvox.SemanticTree.fenceToPunct_(firstOpen);
    974         result.push(firstOpen);
    975       } else {
    976         var split = cvox.SemanticTree.sliceNodes_(openStack, openPred);
    977         var cutLength = split.head.length - 1;
    978         var innerNodes = this.processNeutralFences_(
    979             split.head, contentStack.slice(0, cutLength));
    980         contentStack = contentStack.slice(cutLength);
    981         //var rightContent = contentStack.shift();
    982         result.push.apply(result, innerNodes);
    983         //result.push.apply(result, rightContent);
    984         if (split.div) {
    985           split.tail.unshift(split.div);
    986         }
    987         openStack = split.tail;
    988       }
    989       result.push.apply(result, contentStack.shift());
    990     }
    991     return result;
    992   }
    993   var lastOpen = openStack[openStack.length - 1];
    994   var firstRole = fences[0].role;
    995   // General opening case.
    996   // Either we have an open fence.
    997   if (firstRole == cvox.SemanticAttr.Role.OPEN ||
    998       // Or we have a neutral fence that does not have a counter part.
    999           (firstRole == cvox.SemanticAttr.Role.NEUTRAL &&
   1000               (!lastOpen ||
   1001                   fences[0].textContent != lastOpen.textContent))) {
   1002     openStack.push(fences.shift());
   1003     contentStack.push(content.shift());
   1004     return this.processFences_(fences, content, openStack, contentStack);
   1005   }
   1006   // General closing case.
   1007   if (lastOpen && (
   1008       // Closing fence for some opening fence.
   1009       (firstRole == cvox.SemanticAttr.Role.CLOSE &&
   1010           lastOpen.role == cvox.SemanticAttr.Role.OPEN) ||
   1011               // Netural fence with exact counter part.
   1012               (firstRole == cvox.SemanticAttr.Role.NEUTRAL &&
   1013                   fences[0].textContent == lastOpen.textContent))) {
   1014     var fenced = this.makeHorizontalFencedNode_(
   1015         openStack.pop(), fences.shift(), contentStack.pop());
   1016     contentStack.push(contentStack.pop().concat([fenced], content.shift()));
   1017     return this.processFences_(fences, content, openStack, contentStack);
   1018   }
   1019   // Closing with a neutral fence on the stack.
   1020   if (lastOpen && firstRole == cvox.SemanticAttr.Role.CLOSE &&
   1021       lastOpen.role == cvox.SemanticAttr.Role.NEUTRAL &&
   1022           openStack.some(openPred)) {
   1023     // Steps of the algorithm:
   1024     // 1. Split list at right most opening bracket.
   1025     // 2. Cut content list at corresponding length.
   1026     // 3. Optimise the neutral fences.
   1027     // 4. Make fenced node.
   1028     //
   1029     // Careful, this reverses openStack!
   1030     var split = cvox.SemanticTree.sliceNodes_(openStack, openPred, true);
   1031     // We know that
   1032     // (a) div & tail exist,
   1033     // (b) all are combined in this step into a single fenced node,
   1034     // (c) head is the new openStack,
   1035     // (d) the new contentStack is remainder of contentStack + new fenced node +
   1036     // shift of content.
   1037     var rightContent = contentStack.pop();
   1038     var cutLength = contentStack.length - split.tail.length + 1;
   1039     var innerNodes = this.processNeutralFences_(
   1040         split.tail, contentStack.slice(cutLength));
   1041     contentStack = contentStack.slice(0, cutLength);
   1042     var fenced = this.makeHorizontalFencedNode_(
   1043         split.div, fences.shift(),
   1044         contentStack.pop().concat(innerNodes, rightContent));
   1045     contentStack.push(contentStack.pop().concat([fenced], content.shift()));
   1046     return this.processFences_(fences, content, split.head, contentStack);
   1047   }
   1048   // Final Case: A singular closing fence.
   1049   // We turn the fence into a punctuation.
   1050   var fenced = fences.shift();
   1051   cvox.SemanticTree.fenceToPunct_(fenced);
   1052   contentStack.push(contentStack.pop().concat([fenced], content.shift()));
   1053   return this.processFences_(fences, content, openStack, contentStack);
   1054 };
   1055 
   1056 
   1057 // TODO (sorge) The following could be done with linear programming.
   1058 /**
   1059  * Trys to combine neutral fences as much as possible.
   1060  * @param {!Array.<!cvox.SemanticTree.Node>} fences A list of neutral fences.
   1061  * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} content Intermediate
   1062  *     content. Observe that |content| = |fences| - 1
   1063  * @return {!Array.<cvox.SemanticTree.Node>} List of node with fully fenced
   1064  *     nodes.
   1065  * @private
   1066  */
   1067 cvox.SemanticTree.prototype.processNeutralFences_ = function(fences, content) {
   1068   if (fences.length == 0) {
   1069     return fences;
   1070   }
   1071   if (fences.length == 1) {
   1072     cvox.SemanticTree.fenceToPunct_(fences[0]);
   1073     return fences;
   1074     }
   1075   var firstFence = fences.shift();
   1076   var split = cvox.SemanticTree.sliceNodes_(
   1077       fences, function(x) {return x.textContent == firstFence.textContent;});
   1078   if (!split.div) {
   1079     cvox.SemanticTree.fenceToPunct_(firstFence);
   1080     var restContent = content.shift();
   1081     restContent.unshift(firstFence);
   1082     return restContent.concat(this.processNeutralFences_(fences, content));
   1083   }
   1084   var newContent = this.combineFencedContent_(
   1085       firstFence, split.div, split.head, content);
   1086   if (split.tail.length > 0) {
   1087     var leftContent = newContent.shift();
   1088     var result = this.processNeutralFences_(split.tail, newContent);
   1089     return leftContent.concat(result);
   1090   }
   1091   return newContent[0];
   1092 };
   1093 
   1094 
   1095 /**
   1096  * Combines nodes framed by two matching fences using the given content.
   1097  * Example: leftFence: [, rightFence: ], midFences: |, |
   1098  *          content: c1, c2, c3, c4, ... cn
   1099  *          return: [c1 | c2 | c3 ], c4, ... cn
   1100  * @param {!cvox.SemanticTree.Node} leftFence The left fence.
   1101  * @param {!cvox.SemanticTree.Node} rightFence The right fence.
   1102  * @param {!Array.<cvox.SemanticTree.Node>} midFences A list of intermediate
   1103  *     fences.
   1104  * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} content Intermediate
   1105  *     content. Observe that |content| = |fences| - 1 + k where k >= 0 is the
   1106  *     remainder.
   1107  * @return {!Array.<!Array.<cvox.SemanticTree.Node>>} List of content nodes
   1108  *     where the first is the fully fenced node wrt. the given left and right
   1109  *     fence.
   1110  * @private
   1111  */
   1112 cvox.SemanticTree.prototype.combineFencedContent_ = function(
   1113     leftFence, rightFence, midFences, content) {
   1114 
   1115   if (midFences.length == 0) {
   1116     var fenced = this.makeHorizontalFencedNode_(
   1117         leftFence, rightFence, content.shift());
   1118     content.unshift(fenced);
   1119     return content;
   1120   }
   1121 
   1122   var leftContent = content.shift();
   1123   var cutLength = midFences.length - 1;
   1124   var midContent = content.slice(0, cutLength);
   1125   content = content.slice(cutLength);
   1126   var rightContent = content.shift();
   1127   var innerNodes = this.processNeutralFences_(midFences, midContent);
   1128   leftContent.push.apply(leftContent, innerNodes);
   1129   leftContent.push.apply(leftContent, rightContent);
   1130   var fenced = this.makeHorizontalFencedNode_(
   1131       leftFence, rightFence, leftContent);
   1132   if (content.length > 0) {
   1133     content[0].unshift(fenced);
   1134   } else {
   1135     content = [[fenced]];
   1136   }
   1137   return content;
   1138  };
   1139 
   1140 
   1141 /**
   1142  * Rewrite fences into punctuation. This is done with any "leftover" fence.
   1143  * @param {cvox.SemanticTree.Node} fence Fence.
   1144  * @private
   1145  */
   1146 cvox.SemanticTree.fenceToPunct_ = function(fence) {
   1147   fence.type = cvox.SemanticAttr.Type.PUNCTUATION;
   1148   switch (fence.role) {
   1149     case cvox.SemanticAttr.Role.NEUTRAL:
   1150     fence.role = cvox.SemanticAttr.Role.VBAR;
   1151     break;
   1152     case cvox.SemanticAttr.Role.OPEN:
   1153     fence.role = cvox.SemanticAttr.Role.OPENFENCE;
   1154     break;
   1155     case cvox.SemanticAttr.Role.CLOSE:
   1156     fence.role = cvox.SemanticAttr.Role.CLOSEFENCE;
   1157     break;
   1158   }
   1159 };
   1160 
   1161 
   1162 /**
   1163  * Create a fenced node.
   1164  * @param {cvox.SemanticTree.Node} ofence Opening fence.
   1165  * @param {cvox.SemanticTree.Node} cfence Closing fence.
   1166  * @param {!Array.<cvox.SemanticTree.Node>} content The content
   1167  *     between the fences.
   1168  * @return {!cvox.SemanticTree.Node} The new node.
   1169  * @private
   1170  */
   1171 cvox.SemanticTree.prototype.makeHorizontalFencedNode_ = function(
   1172     ofence, cfence, content) {
   1173   var childNode = this.processRow_(content);
   1174   var newNode = this.makeBranchNode_(
   1175       cvox.SemanticAttr.Type.FENCED, [childNode], [ofence, cfence]);
   1176   if (ofence.role == cvox.SemanticAttr.Role.OPEN) {
   1177     newNode.role = cvox.SemanticAttr.Role.LEFTRIGHT;
   1178   } else {
   1179     newNode.role = ofence.role;
   1180   }
   1181   return newNode;
   1182 };
   1183 
   1184 
   1185 /**
   1186  * Combines sequences of punctuated expressions in a list of nodes.
   1187  * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
   1188  * @return {!Array.<cvox.SemanticTree.Node>} The new list of nodes.
   1189  * @private
   1190  */
   1191 cvox.SemanticTree.prototype.getPunctuationInRow_ = function(nodes) {
   1192   // For now we just make a punctuation node with a particular role. This is
   1193   // similar to an mrow. The only exception are ellipses, which we assume to be
   1194   // in lieu of identifiers.
   1195   // In addition we keep the single punctuation nodes as content.
   1196   var partition = cvox.SemanticTree.partitionNodes_(
   1197       nodes, function(x) {
   1198         return cvox.SemanticTree.attrPred_('type', 'PUNCTUATION')(x) &&
   1199             !cvox.SemanticTree.attrPred_('role', 'ELLIPSIS')(x);});
   1200   if (partition.rel.length == 0) {
   1201     return nodes;
   1202   }
   1203   var newNodes = [];
   1204   var firstComp = partition.comp.shift();
   1205   if (firstComp.length > 0) {
   1206     newNodes.push(this.processRow_(firstComp));
   1207   }
   1208   var relCounter = 0;
   1209   while (partition.comp.length > 0) {
   1210     newNodes.push(partition.rel[relCounter++]);
   1211     firstComp = partition.comp.shift();
   1212     if (firstComp.length > 0) {
   1213       newNodes.push(this.processRow_(firstComp));
   1214     }
   1215   }
   1216   return [this.makePunctuatedNode_(newNodes, partition.rel)];
   1217 };
   1218 
   1219 
   1220 /**
   1221  * Create a punctuated node.
   1222  * @param {!Array.<!cvox.SemanticTree.Node>} nodes List of all nodes separated
   1223  * by punctuations.
   1224  * @param {!Array.<!cvox.SemanticTree.Node>} punctuations List of all separating
   1225  * punctations. Observe that punctations is a subset of nodes.
   1226  * @return {!cvox.SemanticTree.Node}
   1227  * @private
   1228  */
   1229 cvox.SemanticTree.prototype.makePunctuatedNode_ = function(
   1230     nodes, punctuations) {
   1231   var newNode = this.makeBranchNode_(
   1232       cvox.SemanticAttr.Type.PUNCTUATED, nodes, punctuations);
   1233 
   1234   if (punctuations.length == 1 &&
   1235       nodes[0].type == cvox.SemanticAttr.Type.PUNCTUATION) {
   1236     newNode.role = cvox.SemanticAttr.Role.STARTPUNCT;
   1237   } else if (punctuations.length == 1 &&
   1238       nodes[nodes.length - 1].type == cvox.SemanticAttr.Type.PUNCTUATION) {
   1239     newNode.role = cvox.SemanticAttr.Role.ENDPUNCT;
   1240   } else {
   1241     newNode.role = cvox.SemanticAttr.Role.SEQUENCE;
   1242   }
   1243   return newNode;
   1244 };
   1245 
   1246 
   1247 /**
   1248  * Creates a limit node from a sub/superscript or over/under node if the central
   1249  * element is a big operator. Otherwise it creates the standard elements.
   1250  * @param {string} mmlTag The tag name of the original node.
   1251  * @param {!Array.<!cvox.SemanticTree.Node>} children The children of the
   1252  *     original node.
   1253  * @return {!cvox.SemanticTree.Node} The newly created limit node.
   1254  * @private
   1255  */
   1256 cvox.SemanticTree.prototype.makeLimitNode_ = function(mmlTag, children) {
   1257   var center = children[0];
   1258   var isFunction = cvox.SemanticTree.attrPred_('type', 'FUNCTION')(center);
   1259   // TODO (sorge) Put this into a single function.
   1260   var isLimit = cvox.SemanticTree.attrPred_('type', 'LARGEOP')(center) ||
   1261       cvox.SemanticTree.attrPred_('type', 'LIMBOTH')(center) ||
   1262       cvox.SemanticTree.attrPred_('type', 'LIMLOWER')(center) ||
   1263       cvox.SemanticTree.attrPred_('type', 'LIMUPPER')(center) ||
   1264       (isFunction && cvox.SemanticTree.attrPred_('role', 'LIMFUNC')(center));
   1265   var type = cvox.SemanticAttr.Type.UNKNOWN;
   1266   // TODO (sorge) Make use of the difference in information on sub vs under etc.
   1267   if (isLimit) {
   1268     switch (mmlTag) {
   1269       case 'MSUB':
   1270       case 'MUNDER':
   1271       type = cvox.SemanticAttr.Type.LIMLOWER;
   1272       break;
   1273       case 'MSUP':
   1274       case 'MOVER':
   1275       type = cvox.SemanticAttr.Type.LIMUPPER;
   1276       break;
   1277       case 'MSUBSUP':
   1278       case 'MUNDEROVER':
   1279       type = cvox.SemanticAttr.Type.LIMBOTH;
   1280       break;
   1281     }
   1282   } else {
   1283     switch (mmlTag) {
   1284       case 'MSUB':
   1285       type = cvox.SemanticAttr.Type.SUBSCRIPT;
   1286       break;
   1287       case 'MSUP':
   1288       type = cvox.SemanticAttr.Type.SUPERSCRIPT;
   1289       break;
   1290       case 'MSUBSUP':
   1291       var innerNode = this.makeBranchNode_(cvox.SemanticAttr.Type.SUBSCRIPT,
   1292                                       [center, children[1]], []);
   1293       innerNode.role = center.role;
   1294       children = [innerNode, children[2]];
   1295       type = cvox.SemanticAttr.Type.SUPERSCRIPT;
   1296       break;
   1297       case 'MOVER':
   1298       type = cvox.SemanticAttr.Type.OVERSCORE;
   1299       break;
   1300       case 'MUNDER':
   1301       type = cvox.SemanticAttr.Type.UNDERSCORE;
   1302       break;
   1303       case 'MUNDEROVER':
   1304       default:
   1305       var innerNode = this.makeBranchNode_(cvox.SemanticAttr.Type.UNDERSCORE,
   1306                                       [center, children[1]], []);
   1307       innerNode.role = center.role;
   1308       children = [innerNode, children[2]];
   1309       type = cvox.SemanticAttr.Type.OVERSCORE;
   1310       break;
   1311     }
   1312   }
   1313   var newNode = this.makeBranchNode_(type, children, []);
   1314   newNode.role = center.role;
   1315   return newNode;
   1316 };
   1317 
   1318 
   1319 /**
   1320  * Recursive method to accumulate function expressions.
   1321  *
   1322  * The idea is to process functions in a row from left to right combining them
   1323  * with there arguments. Thereby we take the notion of a function rather broadly
   1324  * as a functional expressions that consists of a prefix and some arguments.
   1325  * In particular we distinguish four types of functional expressions:
   1326  * - integral: Integral expression.
   1327  * - bigop: A big operator expression like a sum.
   1328  * - prefix: A well defined prefix function such as sin, cos or a limit
   1329  *           functions like lim, max.
   1330  * - simple: An expression consisting of letters that are potentially a function
   1331  *           symbol. If we have an explicit function application symbol
   1332  *           following the expression we turn into a prefix function. Otherwise
   1333  *           we decide heuristically if we could have a function application.
   1334  * @param {!Array.<cvox.SemanticTree.Node>} restNodes The remainder list of
   1335  *     nodes.
   1336  * @param {!Array.<cvox.SemanticTree.Node>=} result The result node list.
   1337  * @return {!Array.<!cvox.SemanticTree.Node>} The fully processed list.
   1338  * @private
   1339  */
   1340 cvox.SemanticTree.prototype.getFunctionsInRow_ = function(restNodes, result) {
   1341   result = result || [];
   1342   // Base case.
   1343   if (restNodes.length == 0) {
   1344     return result;
   1345   }
   1346   var firstNode = /** @type {!cvox.SemanticTree.Node} */ (restNodes.shift());
   1347   var heuristic = cvox.SemanticTree.classifyFunction_(firstNode, restNodes);
   1348   // First node is not a function node.
   1349   if (!heuristic) {
   1350     result.push(firstNode);
   1351     return this.getFunctionsInRow_(restNodes, result);
   1352   }
   1353   // Combine functions in the rest of the row.
   1354   var processedRest = this.getFunctionsInRow_(restNodes, []);
   1355   var newRest = this.getFunctionArgs_(firstNode, processedRest, heuristic);
   1356   return result.concat(newRest);
   1357 };
   1358 
   1359 
   1360 /**
   1361  * Classifies a function wrt. the heuristic that should be applied.
   1362  * @param {!cvox.SemanticTree.Node} funcNode The node to be classified.
   1363  * @param {!Array.<cvox.SemanticTree.Node>} restNodes The remainder list of
   1364  *     nodes. They can useful to look ahead if there is an explicit function
   1365  *     application. If there is one, it will be destructively removed!
   1366  * @return {!string} The string specifying the heuristic.
   1367  * @private
   1368  */
   1369 cvox.SemanticTree.classifyFunction_ = function(funcNode, restNodes) {
   1370   //  We do not allow double function application. This is not lambda calculus!
   1371   if (funcNode.type == cvox.SemanticAttr.Type.APPL ||
   1372       funcNode.type == cvox.SemanticAttr.Type.BIGOP ||
   1373           funcNode.type == cvox.SemanticAttr.Type.INTEGRAL) {
   1374     return '';
   1375   }
   1376   // Find and remove explicit function applications.
   1377   // We now treat funcNode as a prefix function, regardless of what its actual
   1378   // content is.
   1379   if (restNodes[0] &&
   1380       restNodes[0].textContent == cvox.SemanticAttr.functionApplication()) {
   1381     // Remove explicit function application. This is destructive on the
   1382     // underlying list.
   1383     restNodes.shift();
   1384     cvox.SemanticTree.propagatePrefixFunc_(funcNode);
   1385     return 'prefix';
   1386   }
   1387   switch (funcNode.role) {
   1388     case cvox.SemanticAttr.Role.INTEGRAL:
   1389     return 'integral';
   1390     break;
   1391     case cvox.SemanticAttr.Role.SUM:
   1392     return 'bigop';
   1393     break;
   1394     case cvox.SemanticAttr.Role.PREFIXFUNC:
   1395     case cvox.SemanticAttr.Role.LIMFUNC:
   1396     return 'prefix';
   1397     break;
   1398     default:
   1399     if (funcNode.type == cvox.SemanticAttr.Type.IDENTIFIER) {
   1400       return 'simple';
   1401     }
   1402   }
   1403   return '';
   1404 };
   1405 
   1406 
   1407 /**
   1408  * Propagates a prefix function role in a node.
   1409  * @param {cvox.SemanticTree.Node} funcNode The node whose role is to be
   1410  * rewritten.
   1411  * @private
   1412  */
   1413 cvox.SemanticTree.propagatePrefixFunc_ = function(funcNode) {
   1414   if (funcNode) {
   1415     funcNode.role = cvox.SemanticAttr.Role.PREFIXFUNC;
   1416     cvox.SemanticTree.propagatePrefixFunc_(funcNode.childNodes[0]);
   1417   }
   1418 };
   1419 
   1420 
   1421 /**
   1422  * Computes the arguments for a function from a list of nodes depending on the
   1423  * given heuristic.
   1424  * @param {!cvox.SemanticTree.Node} func A function node.
   1425  * @param {!Array.<cvox.SemanticTree.Node>} rest List of nodes to choose
   1426  *     arguments from.
   1427  * @param {string} heuristic The heuristic to follow.
   1428  * @return {!Array.<!cvox.SemanticTree.Node>} The function and the remainder of
   1429  *     the rest list.
   1430  * @private
   1431  */
   1432 cvox.SemanticTree.prototype.getFunctionArgs_ = function(func, rest, heuristic) {
   1433   switch (heuristic) {
   1434     case 'integral':
   1435     var components = this.getIntegralArgs_(rest);
   1436     var integrand = this.processRow_(components.integrand);
   1437     var funcNode = this.makeIntegralNode_(func, integrand, components.intvar);
   1438     components.rest.unshift(funcNode);
   1439     return components.rest;
   1440     break;
   1441     case 'prefix':
   1442     if (rest[0] && rest[0].type == cvox.SemanticAttr.Type.FENCED) {
   1443       funcNode = this.makeFunctionNode_(
   1444           func, /** @type {!cvox.SemanticTree.Node} */ (rest.shift()));
   1445       rest.unshift(funcNode);
   1446       return rest;
   1447     }
   1448     case 'bigop':
   1449     var partition = cvox.SemanticTree.sliceNodes_(
   1450         rest, cvox.SemanticTree.prefixFunctionBoundary_);
   1451     var arg = this.processRow_(partition.head);
   1452     if (heuristic == 'prefix') {
   1453       funcNode = this.makeFunctionNode_(func, arg);
   1454     } else {
   1455       funcNode = this.makeBigOpNode_(func, arg);
   1456     }
   1457     if (partition.div) {
   1458       partition.tail.unshift(partition.div);
   1459     }
   1460     partition.tail.unshift(funcNode);
   1461     return partition.tail;
   1462     break;
   1463     case 'simple':
   1464     if (rest.length == 0) {
   1465       return [func];
   1466     }
   1467     var firstArg = rest[0];
   1468     if (firstArg.type == cvox.SemanticAttr.Type.FENCED &&
   1469         firstArg.role != cvox.SemanticAttr.Role.NEUTRAL &&
   1470             this.simpleFunctionHeuristic_(firstArg)) {
   1471       funcNode = this.makeFunctionNode_(
   1472           func, /** @type {!cvox.SemanticTree.Node} */ (rest.shift()));
   1473       rest.unshift(funcNode);
   1474       return rest;
   1475     }
   1476     rest.unshift(func);
   1477     return rest;
   1478     break;
   1479   }
   1480 };
   1481 
   1482 
   1483 /**
   1484  * Tail recursive function to obtain integral arguments.
   1485  * @param {!Array.<cvox.SemanticTree.Node>} nodes List of nodes to take
   1486  * arguments from.
   1487  * @param {Array.<cvox.SemanticTree.Node>=} args List of integral arguments.
   1488  * @return {{integrand: !Array.<cvox.SemanticTree.Node>,
   1489  *     intvar: cvox.SemanticTree.Node,
   1490  *     rest: !Array.<cvox.SemanticTree.Node>}}
   1491  *     Result split into integrand, integral variable and the remaining
   1492  *     elements.
   1493  * @private
   1494  */
   1495 cvox.SemanticTree.prototype.getIntegralArgs_ = function(nodes, args) {
   1496   args = args || [];
   1497   if (nodes.length == 0) {
   1498     return {integrand: args, intvar: null, rest: nodes};
   1499   }
   1500   var firstNode = nodes[0];
   1501   if (cvox.SemanticTree.generalFunctionBoundary_(firstNode)) {
   1502     return {integrand: args, intvar: null, rest: nodes};
   1503   }
   1504   if (cvox.SemanticTree.integralDxBoundarySingle_(firstNode)) {
   1505     return {integrand: args, intvar: firstNode, rest: nodes.slice(1)};
   1506   }
   1507   if (nodes[1] && cvox.SemanticTree.integralDxBoundary_(firstNode, nodes[1])) {
   1508     var comma = this.createNode_();
   1509     comma.updateContent_(cvox.SemanticAttr.invisibleComma());
   1510     var intvar = this.makePunctuatedNode_(
   1511         [firstNode, comma, nodes[1]], [comma]);
   1512     intvar.role = cvox.SemanticAttr.Role.INTEGRAL;
   1513     return {integrand: args, intvar: intvar, rest: nodes.slice(2)};
   1514   }
   1515   args.push(nodes.shift());
   1516   return this.getIntegralArgs_(nodes, args);
   1517 };
   1518 
   1519 
   1520 /**
   1521  * Create a function node.
   1522  * @param {!cvox.SemanticTree.Node} func The function operator.
   1523  * @param {!cvox.SemanticTree.Node} arg The argument.
   1524  * @return {!cvox.SemanticTree.Node} The new function node.
   1525  * @private
   1526  */
   1527 cvox.SemanticTree.prototype.makeFunctionNode_ = function(func, arg) {
   1528   var applNode = this.createNode_();
   1529   applNode.updateContent_(cvox.SemanticAttr.functionApplication());
   1530   applNode.type = cvox.SemanticAttr.Type.PUNCTUATION;
   1531   applNode.role = cvox.SemanticAttr.Role.APPLICATION;
   1532   var newNode = this.makeBranchNode_(cvox.SemanticAttr.Type.APPL, [func, arg],
   1533                                 [applNode]);
   1534   newNode.role = func.role;
   1535   return newNode;
   1536 };
   1537 
   1538 
   1539 /**
   1540  * Create a big operator node.
   1541  * @param {!cvox.SemanticTree.Node} bigOp The big operator.
   1542  * @param {!cvox.SemanticTree.Node} arg The argument.
   1543  * @return {!cvox.SemanticTree.Node} The new big operator node.
   1544  * @private
   1545  */
   1546 cvox.SemanticTree.prototype.makeBigOpNode_ = function(bigOp, arg) {
   1547   var newNode = this.makeBranchNode_(
   1548       cvox.SemanticAttr.Type.BIGOP, [bigOp, arg], []);
   1549   newNode.role = bigOp.role;
   1550   return newNode;
   1551 };
   1552 
   1553 
   1554 /**
   1555  * Create an integral node. It has three children: integral, integrand and
   1556  * integration variable. The latter two can be omitted.
   1557  * @param {!cvox.SemanticTree.Node} integral The integral operator.
   1558  * @param {cvox.SemanticTree.Node} integrand The integrand.
   1559  * @param {cvox.SemanticTree.Node} intvar The integral variable.
   1560  * @return {!cvox.SemanticTree.Node} The new integral node.
   1561  * @private
   1562  */
   1563 cvox.SemanticTree.prototype.makeIntegralNode_ = function(
   1564     integral, integrand, intvar) {
   1565   integrand = integrand || this.makeEmptyNode_();
   1566   intvar = intvar || this.makeEmptyNode_();
   1567   var newNode = this.makeBranchNode_(cvox.SemanticAttr.Type.INTEGRAL,
   1568                                 [integral, integrand, intvar], []);
   1569   newNode.role = integral.role;
   1570   return newNode;
   1571 };
   1572 
   1573 
   1574 /**
   1575  * Predicate implementing the boundary criteria for simple functions:
   1576  *
   1577  * @param {!cvox.SemanticTree.Node} node A semantic node of type fenced.
   1578  * @return {boolean} True if the node meets the boundary criteria.
   1579  * @private
   1580  */
   1581 cvox.SemanticTree.prototype.simpleFunctionHeuristic_ = function(node) {
   1582   var children = node.childNodes;
   1583   if (children.length == 0) {
   1584     return true;
   1585   }
   1586   if (children.length > 1) {
   1587     return false;
   1588   }
   1589   var child = children[0];
   1590   if (child.type == cvox.SemanticAttr.Type.INFIXOP) {
   1591     if (child.role != cvox.SemanticAttr.Role.IMPLICIT) {
   1592       return false;
   1593     }
   1594     if (child.childNodes.some(cvox.SemanticTree.attrPred_('type', 'INFIXOP'))) {
   1595       return false;
   1596     }
   1597   }
   1598   return true;
   1599 };
   1600 
   1601 
   1602 /**
   1603  * Predicate implementing the boundary criteria for prefix functions and big
   1604  * operators:
   1605  * 1. an explicit operator,
   1606  * 2. a relation symbol, or
   1607  * 3. some punctuation.
   1608  * @param {cvox.SemanticTree.Node} node A semantic node.
   1609  * @return {boolean} True if the node meets the boundary criteria.
   1610  * @private
   1611  */
   1612 cvox.SemanticTree.prefixFunctionBoundary_ = function(node) {
   1613   return cvox.SemanticTree.attrPred_('type', 'OPERATOR')(node) ||
   1614       cvox.SemanticTree.generalFunctionBoundary_(node);
   1615 };
   1616 
   1617 
   1618 /**
   1619  * Predicate implementing the boundary criteria for integrals dx on two nodes.
   1620  * @param {cvox.SemanticTree.Node} firstNode A semantic node.
   1621  * @param {cvox.SemanticTree.Node} secondNode The direct neighbour of first
   1622  *     Node.
   1623  * @return {boolean} True if the second node exists and the first node is a 'd'.
   1624  * @private
   1625  */
   1626 cvox.SemanticTree.integralDxBoundary_ = function(
   1627     firstNode, secondNode) {
   1628   return !!secondNode &&
   1629       cvox.SemanticTree.attrPred_('type', 'IDENTIFIER')(secondNode) &&
   1630           cvox.SemanticAttr.isCharacterD(firstNode.textContent);
   1631 };
   1632 
   1633 
   1634 /**
   1635  * Predicate implementing the boundary criteria for integrals dx on a single
   1636  * node.
   1637  * @param {cvox.SemanticTree.Node} node A semantic node.
   1638  * @return {boolean} True if the node meets the boundary criteria.
   1639  * @private
   1640  */
   1641 cvox.SemanticTree.integralDxBoundarySingle_ = function(node) {
   1642   if (cvox.SemanticTree.attrPred_('type', 'IDENTIFIER')(node)) {
   1643     var firstChar = node.textContent[0];
   1644     return firstChar && node.textContent[1] &&
   1645         cvox.SemanticAttr.isCharacterD(firstChar);
   1646   }
   1647   return false;
   1648 };
   1649 
   1650 
   1651 /**
   1652  * Predicate implementing the general boundary criteria for function operators:
   1653  * 1. a relation symbol,
   1654  * 2. some punctuation.
   1655  * @param {cvox.SemanticTree.Node} node A semantic node.
   1656  * @return {boolean} True if the node meets the boundary criteria.
   1657  * @private
   1658  */
   1659 cvox.SemanticTree.generalFunctionBoundary_ = function(node) {
   1660   return cvox.SemanticTree.attrPred_('type', 'RELATION')(node) ||
   1661       cvox.SemanticTree.attrPred_('type', 'PUNCTUATION')(node);
   1662 };
   1663 
   1664 
   1665 /**
   1666  * Rewrites tables into matrices or case statements in a list of nodes.
   1667  * @param {!Array.<cvox.SemanticTree.Node>} nodes List of nodes to rewrite.
   1668  * @return {!Array.<cvox.SemanticTree.Node>} The new list of nodes.
   1669  * @private
   1670  */
   1671 cvox.SemanticTree.prototype.processTablesInRow_ = function(nodes) {
   1672   // First we process all matrices:
   1673   var partition = cvox.SemanticTree.partitionNodes_(
   1674       nodes, cvox.SemanticTree.tableIsMatrixOrVector_);
   1675   var result = [];
   1676   for (var i = 0, matrix; matrix = partition.rel[i]; i++) {
   1677     result = result.concat(partition.comp.shift());
   1678     result.push(this.tableToMatrixOrVector_(matrix));
   1679   }
   1680   result = result.concat(partition.comp.shift());
   1681   // Process the remaining tables for cases.
   1682   partition = cvox.SemanticTree.partitionNodes_(
   1683       result, cvox.SemanticTree.isTableOrMultiline_);
   1684   result = [];
   1685   for (var i = 0, table; table = partition.rel[i]; i++) {
   1686     var prevNodes = partition.comp.shift();
   1687     if (cvox.SemanticTree.tableIsCases_(table, prevNodes)) {
   1688       this.tableToCases_(
   1689           table, /** @type {!cvox.SemanticTree.Node} */ (prevNodes.pop()));
   1690     }
   1691     result = result.concat(prevNodes);
   1692     result.push(table);
   1693   }
   1694   return result.concat(partition.comp.shift());
   1695 };
   1696 
   1697 
   1698 /**
   1699  * Decides if a node is a table or multiline element.
   1700  * @param {cvox.SemanticTree.Node} node A node.
   1701  * @return {boolean} True if node is either table or multiline.
   1702  * @private
   1703  */
   1704 cvox.SemanticTree.isTableOrMultiline_ = function(node) {
   1705   return !!node && (cvox.SemanticTree.attrPred_('type', 'TABLE')(node) ||
   1706       cvox.SemanticTree.attrPred_('type', 'MULTILINE')(node));
   1707 };
   1708 
   1709 
   1710 /**
   1711  * Heuristic to decide if we have a matrix: An expression fenced on both sides
   1712  * without any other content is considered a fenced node.
   1713  * @param {cvox.SemanticTree.Node} node A node.
   1714  * @return {boolean} True if we believe we have a matrix.
   1715  * @private
   1716  */
   1717 cvox.SemanticTree.tableIsMatrixOrVector_ = function(node) {
   1718   return !!node && cvox.SemanticTree.attrPred_('type', 'FENCED')(node) &&
   1719       cvox.SemanticTree.attrPred_('role', 'LEFTRIGHT')(node) &&
   1720           node.childNodes.length == 1 &&
   1721               cvox.SemanticTree.isTableOrMultiline_(node.childNodes[0]);
   1722 };
   1723 
   1724 
   1725 /**
   1726  * Replaces a fenced node by a matrix or vector node.
   1727  * @param {!cvox.SemanticTree.Node} node The fenced node to be rewritten.
   1728  * @return {!cvox.SemanticTree.Node} The matrix or vector node.
   1729  * @private
   1730  */
   1731 cvox.SemanticTree.prototype.tableToMatrixOrVector_ = function(node) {
   1732   var matrix = node.childNodes[0];
   1733   var type = cvox.SemanticTree.attrPred_('type', 'MULTILINE')(matrix) ?
   1734       'VECTOR' : 'MATRIX';
   1735   matrix.type = cvox.SemanticAttr.Type[type];
   1736   node.contentNodes.forEach(goog.bind(matrix.appendContentNode_, matrix));
   1737   for (var i = 0, row; row = matrix.childNodes[i]; i++) {
   1738     cvox.SemanticTree.assignRoleToRow_(row, cvox.SemanticAttr.Role[type]);
   1739   }
   1740   return matrix;
   1741 };
   1742 
   1743 
   1744 /**
   1745  * Heuristic to decide if we have a case statement: An expression with a
   1746  * singular open fence before it.
   1747  * @param {!cvox.SemanticTree.Node} table A table node.
   1748  * @param {!Array.<cvox.SemanticTree.Node>} prevNodes A list of previous nodes.
   1749  * @return {boolean} True if we believe we have a case statement.
   1750  * @private
   1751  */
   1752 cvox.SemanticTree.tableIsCases_ = function(table, prevNodes) {
   1753   return prevNodes.length > 0 &&
   1754       cvox.SemanticTree.attrPred_('role', 'OPENFENCE')(
   1755           prevNodes[prevNodes.length - 1]);
   1756 };
   1757 
   1758 
   1759 /**
   1760  * Makes case node out of a table and a fence.
   1761  * @param {!cvox.SemanticTree.Node} table The table containing the cases.
   1762  * @param {!cvox.SemanticTree.Node} openFence The left delimiter of the case
   1763  *     statement.
   1764  * @return {!cvox.SemanticTree.Node} The cases node.
   1765  * @private
   1766  */
   1767 cvox.SemanticTree.prototype.tableToCases_ = function(table, openFence) {
   1768   for (var i = 0, row; row = table.childNodes[i]; i++) {
   1769     cvox.SemanticTree.assignRoleToRow_(row, cvox.SemanticAttr.Role.CASES);
   1770     // }
   1771   }
   1772   table.type = cvox.SemanticAttr.Type.CASES;
   1773   table.appendContentNode_(openFence);
   1774   return table;
   1775 };
   1776 
   1777 
   1778 // TODO (sorge) This heuristic is very primitive. We could start reworking
   1779 // multilines, by combining all cells, semantically rewriting the entire line
   1780 // and see if there are any similarities. Alternatively, we could look for
   1781 // similarities in columns (e.g., single relation symbols, like equalities or
   1782 // inequalities in the same column could indicate an equation array).
   1783 /**
   1784  * Heuristic to decide if we have a multiline formula. A table is considered a
   1785  * multiline formula if it does not have any separate cells.
   1786  * @param {!cvox.SemanticTree.Node} table A table node.
   1787  * @return {boolean} True if we believe we have a mulitline formula.
   1788  * @private
   1789  */
   1790 cvox.SemanticTree.tableIsMultiline_ = function(table) {
   1791   return table.childNodes.every(
   1792       function(row) {
   1793         var length = row.childNodes.length;
   1794         return length <= 1;});
   1795 };
   1796 
   1797 
   1798 /**
   1799  * Rewrites a table to multiline structure, simplifying it by getting rid of the
   1800  * cell hierarchy level.
   1801  * @param {!cvox.SemanticTree.Node} table The node to be rewritten a multiline.
   1802  * @private
   1803  */
   1804 cvox.SemanticTree.prototype.tableToMultiline_ = function(table) {
   1805   table.type = cvox.SemanticAttr.Type.MULTILINE;
   1806   for (var i = 0, row; row = table.childNodes[i]; i++) {
   1807     cvox.SemanticTree.rowToLine_(row, cvox.SemanticAttr.Role.MULTILINE);
   1808   }
   1809 };
   1810 
   1811 
   1812 /**
   1813  * Converts a row that only contains one cell into a single line.
   1814  * @param {!cvox.SemanticTree.Node} row The row to convert.
   1815  * @param {cvox.SemanticAttr.Role=} role The new role for the line.
   1816  * @private
   1817  */
   1818 cvox.SemanticTree.rowToLine_ = function(row, role) {
   1819   role = role || cvox.SemanticAttr.Role.UNKNOWN;
   1820   if (cvox.SemanticTree.attrPred_('type', 'ROW')(row) &&
   1821       row.childNodes.length == 1 &&
   1822           cvox.SemanticTree.attrPred_('type', 'CELL')(row.childNodes[0])) {
   1823     row.type = cvox.SemanticAttr.Type.LINE;
   1824     row.role = role;
   1825     row.childNodes = row.childNodes[0].childNodes;
   1826   }
   1827 };
   1828 
   1829 
   1830 /**
   1831  * Assign a row and its contained cells a new role value.
   1832  * @param {!cvox.SemanticTree.Node} row The row to be updated.
   1833  * @param {!cvox.SemanticAttr.Role} role The new role for the row and its cells.
   1834  * @private
   1835  */
   1836 cvox.SemanticTree.assignRoleToRow_ = function(row, role) {
   1837   if (cvox.SemanticTree.attrPred_('type', 'LINE')(row)) {
   1838     row.role = role;
   1839     return;
   1840   }
   1841   if (cvox.SemanticTree.attrPred_('type', 'ROW')(row)) {
   1842     row.role = role;
   1843     var cellPred = cvox.SemanticTree.attrPred_('type', 'CELL');
   1844     row.childNodes.forEach(function(cell) {
   1845       if (cellPred(cell)) {
   1846         cell.role = role;
   1847       }
   1848     });
   1849   }
   1850 };
   1851 
   1852 
   1853 /**
   1854  * Splits a list of nodes wrt. to a given predicate.
   1855  * @param {Array.<cvox.SemanticTree.Node>} nodes A list of nodes.
   1856  * @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
   1857  *    partitioning relation.
   1858  * @param {boolean=} reverse If true slicing is done from the end.
   1859  * @return {{head: !Array.<cvox.SemanticTree.Node>,
   1860  *           div: cvox.SemanticTree.Node,
   1861  *           tail: !Array.<cvox.SemanticTree.Node>}} The split list.
   1862  * @private
   1863  */
   1864 cvox.SemanticTree.sliceNodes_ = function(nodes, pred, reverse) {
   1865   if (reverse) {
   1866     nodes.reverse();
   1867   }
   1868   var head = [];
   1869   for (var i = 0, node; node = nodes[i]; i++) {
   1870     if (pred(node)) {
   1871       if (reverse) {
   1872         return {head: nodes.slice(i + 1).reverse(),
   1873                 div: node,
   1874                 tail: head.reverse()};
   1875       }
   1876       return {head: head,
   1877               div: node,
   1878               tail: nodes.slice(i + 1)};
   1879     }
   1880     head.push(node);
   1881   }
   1882   if (reverse) {
   1883     return {head: [], div: null, tail: head.reverse()};
   1884   }
   1885   return {head: head, div: null, tail: []};
   1886 };
   1887 
   1888 
   1889 /**
   1890  * Partitions a list of nodes wrt. to a given predicate. Effectively works like
   1891  * a PER on the ordered set of nodes.
   1892  * @param {!Array.<!cvox.SemanticTree.Node>} nodes A list of nodes.
   1893  * @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
   1894  *    partitioning relation.
   1895  * @return {{rel: !Array.<cvox.SemanticTree.Node>,
   1896  *           comp: !Array.<!Array.<cvox.SemanticTree.Node>>}}
   1897  *    The partitioning given in terms of a collection of elements satisfying
   1898  *    the predicate and a collection of complementary sets lying inbetween the
   1899  *    related elements. Observe that we always have |comp| = |rel| + 1.
   1900  *
   1901  * Example: On input [a, r_1, b, c, r_2, d, e, r_3] where P(r_i) holds, we
   1902  *    get as output: {rel: [r_1, r_2, r_3], comp: [[a], [b, c], [d, e], []].
   1903  * @private
   1904  */
   1905 cvox.SemanticTree.partitionNodes_ = function(nodes, pred) {
   1906   var restNodes = nodes;
   1907   var rel = [];
   1908   var comp = [];
   1909 
   1910   do {
   1911     var result = cvox.SemanticTree.sliceNodes_(restNodes, pred);
   1912     comp.push(result.head);
   1913     rel.push(result.div);
   1914     restNodes = result.tail;
   1915   } while (result.div);
   1916   rel.pop();
   1917   return {rel: rel, comp: comp};
   1918 };
   1919 
   1920 
   1921 /**
   1922  * Constructs a predicate to check the semantic attribute of a node.
   1923  * @param {!string} prop The property of a node.
   1924  * @param {!string} attr The attribute.
   1925  * @return {function(cvox.SemanticTree.Node): boolean} The predicate.
   1926  * @private
   1927  */
   1928 
   1929 cvox.SemanticTree.attrPred_ = function(prop, attr) {
   1930   var getAttr = function(prop) {
   1931     switch (prop) {
   1932       case 'type': return cvox.SemanticAttr.Type[attr];
   1933       case 'role': return cvox.SemanticAttr.Role[attr];
   1934       case 'font': return cvox.SemanticAttr.Font[attr];
   1935     }
   1936   };
   1937 
   1938   return function(node) {return node[prop] == getAttr(prop);};
   1939 };
   1940