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.
6 * @fileoverview A semantic tree for MathML expressions.
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
15 goog.provide('cvox.SemanticTree');
16 goog.provide('cvox.SemanticTree.Node');
18 goog.require('cvox.DomUtil');
19 goog.require('cvox.SemanticAttr');
20 goog.require('cvox.SemanticUtil');
24 * Create an initial semantic tree.
25 * @param {!Element} mml The original MathML node.
28 cvox.SemanticTree = function(mml) {
35 /** Original MathML tree.
40 /** @type {cvox.SemanticTree.Node} */
41 this.root = this.parseMathml_(mml);
46 * @param {number} id Node id.
49 cvox.SemanticTree.Node = function(id) {
53 /** @type {Array<Element>} */
56 /** @type {cvox.SemanticTree.Node} */
59 /** @type {cvox.SemanticAttr.Type} */
60 this.type = cvox.SemanticAttr.Type.UNKNOWN;
62 /** @type {cvox.SemanticAttr.Role} */
63 this.role = cvox.SemanticAttr.Role.UNKNOWN;
65 /** @type {cvox.SemanticAttr.Font} */
66 this.font = cvox.SemanticAttr.Font.UNKNOWN;
68 /** @type {!Array<cvox.SemanticTree.Node>} */
72 this.textContent = '';
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>}
78 this.contentNodes = [];
83 * Retrieve all subnodes (including the node itself) that satisfy a given
85 * @param {function(cvox.SemanticTree.Node): boolean} pred The predicate.
86 * @return {!Array<cvox.SemanticTree.Node>} The nodes in the tree for which the
89 cvox.SemanticTree.Node.prototype.querySelectorAll = function(pred) {
91 for (var i = 0, child; child = this.childNodes[i]; i++) {
92 result = result.concat(child.querySelectorAll(pred));
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.
106 cvox.SemanticTree.prototype.xml = function(brief) {
107 var dp = new DOMParser();
108 var xml = dp.parseFromString('<stree></stree>', 'text/xml');
110 var xmlRoot = this.root.xml(xml, brief);
111 xml.childNodes[0].appendChild(xmlRoot);
113 return xml.childNodes[0];
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.
123 cvox.SemanticTree.Node.prototype.xml = function(xml, brief) {
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.
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);
138 var node = xml.createElement(this.type);
140 this.xmlAttributes_(node);
142 node.textContent = this.textContent;
143 if (this.contentNodes.length > 0) {
144 node.appendChild(xmlNodeList('content', this.contentNodes));
146 if (this.childNodes.length > 0) {
147 node.appendChild(xmlNodeList('children', this.childNodes));
154 * Serializes the XML representation of the tree.
155 * @param {boolean=} brief If set attributes are omitted.
156 * @return {string} Serialized string.
158 cvox.SemanticTree.prototype.toString = function(brief) {
159 var xmls = new XMLSerializer();
160 return xmls.serializeToString(this.xml(brief));
165 * Pretty print the XML representation of the tree.
166 * @param {boolean=} brief If set attributes are omitted.
167 * @return {string} The formatted string.
169 cvox.SemanticTree.prototype.formatXml = function(brief) {
170 var xml = this.toString(brief);
171 return cvox.SemanticTree.formatXml(xml);
176 * Pretty prints an XML representation.
177 * @param {string} xml The serialised XML string.
178 * @return {string} The formatted string.
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');
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/)) {
195 padding = padding.slice(2);
196 formatted += padding + node + '\r\n';
198 } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
200 formatted += padding + node + '\r\n';
204 formatted += padding + node + '\r\n';
212 * Serializes the XML representation of a node.
213 * @param {boolean=} brief If attributes are to be omitted.
214 * @return {string} Serialized string.
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));
225 * Adds attributes to the XML representation of the current node.
226 * @param {Node} node The XML node.
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);
234 node.setAttribute('id', this.id);
238 /** Creates a new node object.
239 * @return {cvox.SemanticTree.Node} The newly created node.
242 cvox.SemanticTree.prototype.createNode_ = function() {
243 return new cvox.SemanticTree.Node(this.idCounter_++);
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.
253 cvox.SemanticTree.prototype.replaceNode_ = function(oldNode, newNode) {
254 var parent = oldNode.parent;
259 parent.replaceChild_(oldNode, newNode);
264 * Updates the content of the node thereby possibly changing type and role.
265 * @param {string} content The new content string.
268 cvox.SemanticTree.Node.prototype.updateContent_ = function(content) {
269 // Remove superfluous whitespace!
270 content = content.trim();
271 if (this.textContent == content) {
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;
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.
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);
299 * Removes MathML nodes from the node's store of MathML nodes.
300 * @param {Array<Node>} mmlNodes List of MathML nodes.
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);
308 mmlList.splice(index, 1);
311 this.mathml = mmlList;
316 * Appends a child to the node.
317 * @param {cvox.SemanticTree.Node} child The new child.
320 cvox.SemanticTree.Node.prototype.appendChild_ = function(child) {
321 this.childNodes.push(child);
322 this.addMathmlNodes_(child.mathml);
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.
333 cvox.SemanticTree.Node.prototype.replaceChild_ = function(oldNode, newNode) {
334 var index = this.childNodes.indexOf(oldNode);
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
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);
354 * Appends a content node to the node.
355 * @param {cvox.SemanticTree.Node} node The new content node.
358 cvox.SemanticTree.Node.prototype.appendContentNode_ = function(node) {
360 this.contentNodes.push(node);
361 this.addMathmlNodes_(node.mathml);
368 * Removes a content node from the node.
369 * @param {cvox.SemanticTree.Node} node The content node to be removed.
372 cvox.SemanticTree.Node.prototype.removeContentNode_ = function(node) {
374 var index = this.contentNodes.indexOf(node);
376 this.contentNodes.splice(index, 1);
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.
389 cvox.SemanticTree.prototype.parseMathml_ = function(mml) {
390 var children = cvox.DomUtil.toArray(mml.children);
391 switch (cvox.SemanticUtil.tagName(mml)) {
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]));
401 // Case of a 'meaningful' row, even if they are empty.
402 return this.processRow_(this.parseMathmlChildren_(children));
405 var newNode = this.makeBranchNode_(
406 cvox.SemanticAttr.Type.FRACTION,
407 [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
409 newNode.role = cvox.SemanticAttr.Role.DIVISION;
418 return this.makeLimitNode_(cvox.SemanticUtil.tagName(mml),
419 this.parseMathmlChildren_(children));
422 return this.makeBranchNode_(
423 cvox.SemanticAttr.Type.ROOT,
424 [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
428 children = this.parseMathmlChildren_(
429 cvox.SemanticUtil.purgeNodes(children));
430 return this.makeBranchNode_(
431 cvox.SemanticAttr.Type.SQRT, [this.processRow_(children)], []);
434 newNode = this.makeBranchNode_(
435 cvox.SemanticAttr.Type.TABLE,
436 this.parseMathmlChildren_(children), []);
437 if (cvox.SemanticTree.tableIsMultiline_(newNode)) {
438 this.tableToMultiline_(newNode);
443 newNode = this.makeBranchNode_(
444 cvox.SemanticAttr.Type.ROW,
445 this.parseMathmlChildren_(children), []);
446 newNode.role = cvox.SemanticAttr.Role.TABLE;
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;
458 var leaf = this.makeLeafNode_(mml);
459 leaf.type = cvox.SemanticAttr.Type.TEXT;
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.
466 leaf = this.makeLeafNode_(mml);
467 if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
468 leaf.type = cvox.SemanticAttr.Type.IDENTIFIER;
473 leaf = this.makeLeafNode_(mml);
474 if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
475 leaf.type = cvox.SemanticAttr.Type.NUMBER;
480 leaf = this.makeLeafNode_(mml);
481 if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
482 leaf.type = cvox.SemanticAttr.Type.OPERATOR;
486 // TODO (sorge) Do something useful with error and phantom symbols.
488 // Ordinarilly at this point we should not get any other tag.
489 return this.makeUnprocessed_(mml);
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
501 cvox.SemanticTree.prototype.parseMathmlChildren_ = function(mmls) {
503 for (var i = 0, mml; mml = mmls[i]; i++) {
504 result.push(this.parseMathml_(mml));
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.
515 cvox.SemanticTree.prototype.makeUnprocessed_ = function(mml) {
516 var node = this.createNode_();
523 * Create an empty leaf node.
524 * @return {!cvox.SemanticTree.Node} The new node.
527 cvox.SemanticTree.prototype.makeEmptyNode_ = function() {
528 var node = this.createNode_();
529 node.type = cvox.SemanticAttr.Type.EMPTY;
535 * Create a leaf node.
536 * @param {Node} mml The MathML tree.
537 * @return {!cvox.SemanticTree.Node} The new node.
540 cvox.SemanticTree.prototype.makeLeafNode_ = function(mml) {
541 var node = this.createNode_();
543 node.updateContent_(mml.textContent);
544 node.font = mml.getAttribute('mathvariant') || node.font;
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.
558 cvox.SemanticTree.prototype.makeBranchNode_ = function(
559 type, children, contentNodes, content) {
560 var node = this.createNode_();
562 node.updateContent_(content);
565 node.childNodes = children;
566 node.contentNodes = contentNodes;
567 children.concat(contentNodes)
571 node.addMathmlNodes_(x.mathml);
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.
584 cvox.SemanticTree.prototype.makeImplicitNode_ = function(nodes) {
585 if (nodes.length == 1) {
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;
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.
604 cvox.SemanticTree.prototype.makeInfixNode_ = function(children, opNode) {
605 return this.makeBranchNode_(
606 cvox.SemanticAttr.Type.INFIXOP, children, [opNode], opNode.textContent);
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.
620 cvox.SemanticTree.prototype.makeConcatNode_ = function(inner, nodeList, type) {
621 if (nodeList.length == 0) {
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;
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.
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);
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);
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.
670 cvox.SemanticTree.prototype.makePostfixNode_ = function(node, postfixes) {
671 return this.makeConcatNode_(
672 node, postfixes, cvox.SemanticAttr.Type.POSTFIXOP);
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.
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.
684 * This is the main heuristic to rewrite a flat row of terms into a meaningful
686 * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
687 * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
690 cvox.SemanticTree.prototype.processRow_ = function(nodes) {
691 if (nodes.length == 0) {
692 return this.makeEmptyNode_();
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);
703 * Constructs a syntax tree with relation and operator precedence from a list
705 * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
706 * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
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];
715 return this.processOperationsInRow_(nodes);
717 if (nodes.length == 1) {
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);
728 return this.makeBranchNode_(
729 cvox.SemanticAttr.Type.MULTIREL, children, partition.rel);
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.
739 cvox.SemanticTree.prototype.processOperationsInRow_ = function(nodes) {
740 if (nodes.length == 0) {
741 return this.makeEmptyNode_();
743 if (nodes.length == 1) {
748 while (nodes.length > 0 &&
749 nodes[0].type == cvox.SemanticAttr.Type.OPERATOR) {
750 prefix.push(nodes.shift());
752 // Pathological case: only operators in row.
753 if (nodes.length == 0) {
754 return this.makePrefixNode_(prefix.pop(), prefix);
756 if (nodes.length == 1) {
757 return this.makePrefixNode_(nodes[0], prefix);
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)),
770 return this.makeOperationsTree_(split.tail, node, split.div);
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
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.
786 cvox.SemanticTree.prototype.makeOperationsTree_ = function(
787 nodes, root, lastop, prefixes) {
788 prefixes = prefixes || [];
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()),
799 root.appendChild_(node);
802 return this.makePostfixNode_(root, prefixes);
805 var split = cvox.SemanticTree.sliceNodes_(
806 nodes, cvox.SemanticTree.attrPred_('type', 'OPERATOR'));
808 if (split.head.length == 0) {
809 prefixes.push(split.div);
810 return this.makeOperationsTree_(split.tail, root, lastop, prefixes);
813 var node = this.makePrefixNode_(
814 this.makeImplicitNode_(split.head), prefixes);
815 var newNode = this.appendOperand_(root, lastop, node);
820 return this.makeOperationsTree_(split.tail, newNode, split.div, []);
823 // TODO (sorge) The following four functions could be combined into
824 // a single one. Currently it is clearer the way it is, though.
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.
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);
839 if (this.appendExistingOperator_(root, op, node)) {
842 return op.role == cvox.SemanticAttr.Role.MULTIPLICATION ?
843 this.appendMultiplicativeOp_(root, op, node) :
844 this.appendAdditiveOp_(root, op, node);
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.
856 cvox.SemanticTree.prototype.appendMultiplicativeOp_ = function(root, op, node) {
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];
863 var newNode = this.makeInfixNode_([lastRoot.childNodes.pop(), node], op);
864 lastRoot.appendChild_(newNode);
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.
877 cvox.SemanticTree.prototype.appendAdditiveOp_ = function(root, op, node) {
878 return this.makeInfixNode_([root, node], op);
883 * Adds an operand to an operator node if it is the continuation of an existing
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.
891 cvox.SemanticTree.prototype.appendExistingOperator_ = function(root, op, node) {
892 if (!root || root.type != cvox.SemanticAttr.Type.INFIXOP) {
895 if (root.textContent == op.textContent) {
896 root.appendContentNode_(op);
897 root.appendChild_(node);
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]),
908 // TODO (sorge) The following procedure needs a rational reconstruction. It
909 // contains a number of similar cases which should be combined.
911 * Combines delimited expressions in a list of nodes.
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.
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]);
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
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.
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];
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) {
957 // - make punctuation nodes from open fences
958 // - combine as many neutral fences as possible, if the are not separated by
960 // The idea is to allow for things like case statements etc. and not bury
961 // them inside a neutral fenced expression.
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);
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);
985 split.tail.unshift(split.div);
987 openStack = split.tail;
989 result.push.apply(result, contentStack.shift());
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 &&
1001 fences[0].textContent != lastOpen.textContent))) {
1002 openStack.push(fences.shift());
1003 contentStack.push(content.shift());
1004 return this.processFences_(fences, content, openStack, contentStack);
1006 // General closing case.
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);
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.
1029 // Careful, this reverses openStack!
1030 var split = cvox.SemanticTree.sliceNodes_(openStack, openPred, true);
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);
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);
1057 // TODO (sorge) The following could be done with linear programming.
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
1067 cvox.SemanticTree.prototype.processNeutralFences_ = function(fences, content) {
1068 if (fences.length == 0) {
1071 if (fences.length == 1) {
1072 cvox.SemanticTree.fenceToPunct_(fences[0]);
1075 var firstFence = fences.shift();
1076 var split = cvox.SemanticTree.sliceNodes_(
1077 fences, function(x) {return x.textContent == firstFence.textContent;});
1079 cvox.SemanticTree.fenceToPunct_(firstFence);
1080 var restContent = content.shift();
1081 restContent.unshift(firstFence);
1082 return restContent.concat(this.processNeutralFences_(fences, content));
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);
1091 return newContent[0];
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
1104 * @param {!Array<!Array<cvox.SemanticTree.Node>>} content Intermediate
1105 * content. Observe that |content| = |fences| - 1 + k where k >= 0 is the
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
1112 cvox.SemanticTree.prototype.combineFencedContent_ = function(
1113 leftFence, rightFence, midFences, content) {
1115 if (midFences.length == 0) {
1116 var fenced = this.makeHorizontalFencedNode_(
1117 leftFence, rightFence, content.shift());
1118 content.unshift(fenced);
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);
1135 content = [[fenced]];
1142 * Rewrite fences into punctuation. This is done with any "leftover" fence.
1143 * @param {cvox.SemanticTree.Node} fence Fence.
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;
1152 case cvox.SemanticAttr.Role.OPEN:
1153 fence.role = cvox.SemanticAttr.Role.OPENFENCE;
1155 case cvox.SemanticAttr.Role.CLOSE:
1156 fence.role = cvox.SemanticAttr.Role.CLOSEFENCE;
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.
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;
1179 newNode.role = ofence.role;
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.
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) {
1204 var firstComp = partition.comp.shift();
1205 if (firstComp.length > 0) {
1206 newNodes.push(this.processRow_(firstComp));
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));
1216 return [this.makePunctuatedNode_(newNodes, partition.rel)];
1221 * Create a punctuated node.
1222 * @param {!Array<!cvox.SemanticTree.Node>} nodes List of all nodes separated
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}
1229 cvox.SemanticTree.prototype.makePunctuatedNode_ = function(
1230 nodes, punctuations) {
1231 var newNode = this.makeBranchNode_(
1232 cvox.SemanticAttr.Type.PUNCTUATED, nodes, punctuations);
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;
1241 newNode.role = cvox.SemanticAttr.Role.SEQUENCE;
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
1253 * @return {!cvox.SemanticTree.Node} The newly created limit node.
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.
1271 type = cvox.SemanticAttr.Type.LIMLOWER;
1275 type = cvox.SemanticAttr.Type.LIMUPPER;
1279 type = cvox.SemanticAttr.Type.LIMBOTH;
1285 type = cvox.SemanticAttr.Type.SUBSCRIPT;
1288 type = cvox.SemanticAttr.Type.SUPERSCRIPT;
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;
1298 type = cvox.SemanticAttr.Type.OVERSCORE;
1301 type = cvox.SemanticAttr.Type.UNDERSCORE;
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;
1313 var newNode = this.makeBranchNode_(type, children, []);
1314 newNode.role = center.role;
1320 * Recursive method to accumulate function expressions.
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
1336 * @param {!Array<cvox.SemanticTree.Node>=} result The result node list.
1337 * @return {!Array<!cvox.SemanticTree.Node>} The fully processed list.
1340 cvox.SemanticTree.prototype.getFunctionsInRow_ = function(restNodes, result) {
1341 result = result || [];
1343 if (restNodes.length == 0) {
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.
1350 result.push(firstNode);
1351 return this.getFunctionsInRow_(restNodes, result);
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);
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.
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) {
1376 // Find and remove explicit function applications.
1377 // We now treat funcNode as a prefix function, regardless of what its actual
1380 restNodes[0].textContent == cvox.SemanticAttr.functionApplication()) {
1381 // Remove explicit function application. This is destructive on the
1384 cvox.SemanticTree.propagatePrefixFunc_(funcNode);
1387 switch (funcNode.role) {
1388 case cvox.SemanticAttr.Role.INTEGRAL:
1391 case cvox.SemanticAttr.Role.SUM:
1394 case cvox.SemanticAttr.Role.PREFIXFUNC:
1395 case cvox.SemanticAttr.Role.LIMFUNC:
1399 if (funcNode.type == cvox.SemanticAttr.Type.IDENTIFIER) {
1408 * Propagates a prefix function role in a node.
1409 * @param {cvox.SemanticTree.Node} funcNode The node whose role is to be
1413 cvox.SemanticTree.propagatePrefixFunc_ = function(funcNode) {
1415 funcNode.role = cvox.SemanticAttr.Role.PREFIXFUNC;
1416 cvox.SemanticTree.propagatePrefixFunc_(funcNode.childNodes[0]);
1422 * Computes the arguments for a function from a list of nodes depending on the
1424 * @param {!cvox.SemanticTree.Node} func A function node.
1425 * @param {!Array<cvox.SemanticTree.Node>} rest List of nodes to choose
1427 * @param {string} heuristic The heuristic to follow.
1428 * @return {!Array<!cvox.SemanticTree.Node>} The function and the remainder of
1432 cvox.SemanticTree.prototype.getFunctionArgs_ = function(func, rest, heuristic) {
1433 switch (heuristic) {
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;
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);
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);
1455 funcNode = this.makeBigOpNode_(func, arg);
1457 if (partition.div) {
1458 partition.tail.unshift(partition.div);
1460 partition.tail.unshift(funcNode);
1461 return partition.tail;
1464 if (rest.length == 0) {
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);
1484 * Tail recursive function to obtain integral arguments.
1485 * @param {!Array<cvox.SemanticTree.Node>} nodes List of nodes to take
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
1495 cvox.SemanticTree.prototype.getIntegralArgs_ = function(nodes, args) {
1497 if (nodes.length == 0) {
1498 return {integrand: args, intvar: null, rest: nodes};
1500 var firstNode = nodes[0];
1501 if (cvox.SemanticTree.generalFunctionBoundary_(firstNode)) {
1502 return {integrand: args, intvar: null, rest: nodes};
1504 if (cvox.SemanticTree.integralDxBoundarySingle_(firstNode)) {
1505 return {integrand: args, intvar: firstNode, rest: nodes.slice(1)};
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)};
1515 args.push(nodes.shift());
1516 return this.getIntegralArgs_(nodes, args);
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.
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],
1534 newNode.role = func.role;
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.
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;
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.
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;
1575 * Predicate implementing the boundary criteria for simple functions:
1577 * @param {!cvox.SemanticTree.Node} node A semantic node of type fenced.
1578 * @return {boolean} True if the node meets the boundary criteria.
1581 cvox.SemanticTree.prototype.simpleFunctionHeuristic_ = function(node) {
1582 var children = node.childNodes;
1583 if (children.length == 0) {
1586 if (children.length > 1) {
1589 var child = children[0];
1590 if (child.type == cvox.SemanticAttr.Type.INFIXOP) {
1591 if (child.role != cvox.SemanticAttr.Role.IMPLICIT) {
1594 if (child.childNodes.some(cvox.SemanticTree.attrPred_('type', 'INFIXOP'))) {
1603 * Predicate implementing the boundary criteria for prefix functions and big
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.
1612 cvox.SemanticTree.prefixFunctionBoundary_ = function(node) {
1613 return cvox.SemanticTree.attrPred_('type', 'OPERATOR')(node) ||
1614 cvox.SemanticTree.generalFunctionBoundary_(node);
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
1623 * @return {boolean} True if the second node exists and the first node is a 'd'.
1626 cvox.SemanticTree.integralDxBoundary_ = function(
1627 firstNode, secondNode) {
1628 return !!secondNode &&
1629 cvox.SemanticTree.attrPred_('type', 'IDENTIFIER')(secondNode) &&
1630 cvox.SemanticAttr.isCharacterD(firstNode.textContent);
1635 * Predicate implementing the boundary criteria for integrals dx on a single
1637 * @param {cvox.SemanticTree.Node} node A semantic node.
1638 * @return {boolean} True if the node meets the boundary criteria.
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);
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.
1659 cvox.SemanticTree.generalFunctionBoundary_ = function(node) {
1660 return cvox.SemanticTree.attrPred_('type', 'RELATION')(node) ||
1661 cvox.SemanticTree.attrPred_('type', 'PUNCTUATION')(node);
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.
1671 cvox.SemanticTree.prototype.processTablesInRow_ = function(nodes) {
1672 // First we process all matrices:
1673 var partition = cvox.SemanticTree.partitionNodes_(
1674 nodes, cvox.SemanticTree.tableIsMatrixOrVector_);
1676 for (var i = 0, matrix; matrix = partition.rel[i]; i++) {
1677 result = result.concat(partition.comp.shift());
1678 result.push(this.tableToMatrixOrVector_(matrix));
1680 result = result.concat(partition.comp.shift());
1681 // Process the remaining tables for cases.
1682 partition = cvox.SemanticTree.partitionNodes_(
1683 result, cvox.SemanticTree.isTableOrMultiline_);
1685 for (var i = 0, table; table = partition.rel[i]; i++) {
1686 var prevNodes = partition.comp.shift();
1687 if (cvox.SemanticTree.tableIsCases_(table, prevNodes)) {
1689 table, /** @type {!cvox.SemanticTree.Node} */ (prevNodes.pop()));
1691 result = result.concat(prevNodes);
1694 return result.concat(partition.comp.shift());
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.
1704 cvox.SemanticTree.isTableOrMultiline_ = function(node) {
1705 return !!node && (cvox.SemanticTree.attrPred_('type', 'TABLE')(node) ||
1706 cvox.SemanticTree.attrPred_('type', 'MULTILINE')(node));
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.
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]);
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.
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]);
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.
1752 cvox.SemanticTree.tableIsCases_ = function(table, prevNodes) {
1753 return prevNodes.length > 0 &&
1754 cvox.SemanticTree.attrPred_('role', 'OPENFENCE')(
1755 prevNodes[prevNodes.length - 1]);
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
1764 * @return {!cvox.SemanticTree.Node} The cases node.
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);
1772 table.type = cvox.SemanticAttr.Type.CASES;
1773 table.appendContentNode_(openFence);
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).
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.
1790 cvox.SemanticTree.tableIsMultiline_ = function(table) {
1791 return table.childNodes.every(
1793 var length = row.childNodes.length;
1794 return length <= 1;});
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.
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);
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.
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;
1825 row.childNodes = row.childNodes[0].childNodes;
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.
1836 cvox.SemanticTree.assignRoleToRow_ = function(row, role) {
1837 if (cvox.SemanticTree.attrPred_('type', 'LINE')(row)) {
1841 if (cvox.SemanticTree.attrPred_('type', 'ROW')(row)) {
1843 var cellPred = cvox.SemanticTree.attrPred_('type', 'CELL');
1844 row.childNodes.forEach(function(cell) {
1845 if (cellPred(cell)) {
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.
1864 cvox.SemanticTree.sliceNodes_ = function(nodes, pred, reverse) {
1869 for (var i = 0, node; node = nodes[i]; i++) {
1872 return {head: nodes.slice(i + 1).reverse(),
1874 tail: head.reverse()};
1878 tail: nodes.slice(i + 1)};
1883 return {head: [], div: null, tail: head.reverse()};
1885 return {head: head, div: null, tail: []};
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.
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], []].
1905 cvox.SemanticTree.partitionNodes_ = function(nodes, pred) {
1906 var restNodes = nodes;
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);
1917 return {rel: rel, comp: comp};
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.
1929 cvox.SemanticTree.attrPred_ = function(prop, attr) {
1930 var getAttr = function(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];
1938 return function(node) {return node[prop] == getAttr(prop);};