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
);};