summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js')
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js1939
1 files changed, 0 insertions, 1939 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
deleted file mode 100644
index 991ae3244ca..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
+++ /dev/null
@@ -1,1939 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A semantic tree for MathML expressions.
- *
- * This file contains functionality to compute a semantic interpretation from a
- * given MathML expression. This is a very heuristic approach that assumes a
- * fairly simple default semantic which is suitable for K-12 and simple UG
- * mathematics.
- *
- */
-
-goog.provide('cvox.SemanticTree');
-goog.provide('cvox.SemanticTree.Node');
-
-goog.require('cvox.DomUtil');
-goog.require('cvox.SemanticAttr');
-goog.require('cvox.SemanticUtil');
-
-
-/**
- * Create an initial semantic tree.
- * @param {!Element} mml The original MathML node.
- * @constructor
- */
-cvox.SemanticTree = function(mml) {
- /** ID counter.
- * @type {number}
- * @private
- */
- this.idCounter_ = 0;
-
- /** Original MathML tree.
- * @type {Node}
- */
- this.mathml = mml;
-
- /** @type {cvox.SemanticTree.Node} */
- this.root = this.parseMathml_(mml);
-};
-
-
-/**
- * @param {number} id Node id.
- * @constructor
- */
-cvox.SemanticTree.Node = function(id) {
- /** @type {number} */
- this.id = id;
-
- /** @type {Array<Element>} */
- this.mathml = [];
-
- /** @type {cvox.SemanticTree.Node} */
- this.parent = null;
-
- /** @type {cvox.SemanticAttr.Type} */
- this.type = cvox.SemanticAttr.Type.UNKNOWN;
-
- /** @type {cvox.SemanticAttr.Role} */
- this.role = cvox.SemanticAttr.Role.UNKNOWN;
-
- /** @type {cvox.SemanticAttr.Font} */
- this.font = cvox.SemanticAttr.Font.UNKNOWN;
-
- /** @type {!Array<cvox.SemanticTree.Node>} */
- this.childNodes = [];
-
- /** @type {string} */
- this.textContent = '';
-
- /** Branch nodes can store additional nodes that can be useful.
- * E.g. a node of type FENCED can have the opening and closing fences here.
- * @type {!Array<cvox.SemanticTree.Node>}
- */
- this.contentNodes = [];
-};
-
-
-/**
- * Retrieve all subnodes (including the node itself) that satisfy a given
- * predicate.
- * @param {function(cvox.SemanticTree.Node): boolean} pred The predicate.
- * @return {!Array<cvox.SemanticTree.Node>} The nodes in the tree for which the
- * predicate holds.
- */
-cvox.SemanticTree.Node.prototype.querySelectorAll = function(pred) {
- var result = [];
- for (var i = 0, child; child = this.childNodes[i]; i++) {
- result = result.concat(child.querySelectorAll(pred));
- }
- if (pred(this)) {
- result.unshift(this);
- }
- return result;
-};
-
-
- /**
- * Returns an XML representation of the tree.
- * @param {boolean=} brief If set attributes are omitted.
- * @return {Node} The XML representation of the tree.
- */
- cvox.SemanticTree.prototype.xml = function(brief) {
- var dp = new DOMParser();
- var xml = dp.parseFromString('<stree></stree>', 'text/xml');
-
- var xmlRoot = this.root.xml(xml, brief);
- xml.childNodes[0].appendChild(xmlRoot);
-
- return xml.childNodes[0];
- };
-
-
- /**
- * An XML tree representation of the current node.
- * @param {Document} xml The XML document.
- * @param {boolean=} brief If set attributes are omitted.
- * @return {Node} The XML representation of the node.
- */
- cvox.SemanticTree.Node.prototype.xml = function(xml, brief) {
- /**
- * Translates a list of nodes into XML representation.
- * @param {string} tag Name of the enclosing tag.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes A list of nodes.
- * @return {Node} An XML representation of the node list.
- */
- var xmlNodeList = function(tag, nodes) {
- var xmlNodes = nodes.map(function(x) {return x.xml(xml, brief);});
- var tagNode = xml.createElement(tag);
- for (var i = 0, child; child = xmlNodes[i]; i++) {
- tagNode.appendChild(child);
- }
- return tagNode;
- };
- var node = xml.createElement(this.type);
- if (!brief) {
- this.xmlAttributes_(node);
- }
- node.textContent = this.textContent;
- if (this.contentNodes.length > 0) {
- node.appendChild(xmlNodeList('content', this.contentNodes));
- }
- if (this.childNodes.length > 0) {
- node.appendChild(xmlNodeList('children', this.childNodes));
- }
- return node;
- };
-
-
-/**
- * Serializes the XML representation of the tree.
- * @param {boolean=} brief If set attributes are omitted.
- * @return {string} Serialized string.
- */
-cvox.SemanticTree.prototype.toString = function(brief) {
- var xmls = new XMLSerializer();
- return xmls.serializeToString(this.xml(brief));
-};
-
-
-/**
- * Pretty print the XML representation of the tree.
- * @param {boolean=} brief If set attributes are omitted.
- * @return {string} The formatted string.
- */
-cvox.SemanticTree.prototype.formatXml = function(brief) {
- var xml = this.toString(brief);
- return cvox.SemanticTree.formatXml(xml);
-};
-
-
-/**
- * Pretty prints an XML representation.
- * @param {string} xml The serialised XML string.
- * @return {string} The formatted string.
- */
-cvox.SemanticTree.formatXml = function(xml) {
- var reg = /(>)(<)(\/*)/g;
- xml = xml.replace(reg, '$1\r\n$2$3');
- reg = /(>)(.+)(<c)/g;
- xml = xml.replace(reg, '$1\r\n$2\r\n$3');
- var formatted = '';
- var padding = '';
- xml.split('\r\n')
- .forEach(function(node) {
- if (node.match(/.+<\/\w[^>]*>$/)) {
- // Node with content.
- formatted += padding + node + '\r\n';
- } else if (node.match(/^<\/\w/)) {
- if (padding) {
- // Closing tag
- padding = padding.slice(2);
- formatted += padding + node + '\r\n';
- }
- } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
- // Opening tag
- formatted += padding + node + '\r\n';
- padding += ' ';
- } else {
- // Empty tag
- formatted += padding + node + '\r\n';
- }
- });
- return formatted;
-};
-
-
-/**
- * Serializes the XML representation of a node.
- * @param {boolean=} brief If attributes are to be omitted.
- * @return {string} Serialized string.
- */
-cvox.SemanticTree.Node.prototype.toString = function(brief) {
- var xmls = new XMLSerializer();
- var dp = new DOMParser();
- var xml = dp.parseFromString('', 'text/xml');
- return xmls.serializeToString(this.xml(xml, brief));
-};
-
-
-/**
- * Adds attributes to the XML representation of the current node.
- * @param {Node} node The XML node.
- * @private
- */
-cvox.SemanticTree.Node.prototype.xmlAttributes_ = function(node) {
- node.setAttribute('role', this.role);
- if (this.font != cvox.SemanticAttr.Font.UNKNOWN) {
- node.setAttribute('font', this.font);
- }
- node.setAttribute('id', this.id);
-};
-
-
-/** Creates a new node object.
- * @return {cvox.SemanticTree.Node} The newly created node.
- * @private
- */
-cvox.SemanticTree.prototype.createNode_ = function() {
- return new cvox.SemanticTree.Node(this.idCounter_++);
-};
-
-
-/**
- * Replaces a node in the tree. Updates the root node if necessary.
- * @param {!cvox.SemanticTree.Node} oldNode The node to be replaced.
- * @param {!cvox.SemanticTree.Node} newNode The new node.
- * @private
- */
-cvox.SemanticTree.prototype.replaceNode_ = function(oldNode, newNode) {
- var parent = oldNode.parent;
- if (!parent) {
- this.root = newNode;
- return;
- }
- parent.replaceChild_(oldNode, newNode);
-};
-
-
-/**
- * Updates the content of the node thereby possibly changing type and role.
- * @param {string} content The new content string.
- * @private
- */
-cvox.SemanticTree.Node.prototype.updateContent_ = function(content) {
- // Remove superfluous whitespace!
- content = content.trim();
- if (this.textContent == content) {
- return;
- }
- var meaning = cvox.SemanticAttr.lookupMeaning(content);
- this.textContent = content;
- this.role = meaning.role;
- this.type = meaning.type;
- this.font = meaning.font;
-};
-
-
-/**
- * Adds MathML nodes to the node's store of MathML nodes if necessary only, as
- * we can not necessarily assume that the MathML of the content nodes and
- * children are all disjoint.
- * @param {Array<Node>} mmlNodes List of MathML nodes.
- * @private
- */
-cvox.SemanticTree.Node.prototype.addMathmlNodes_ = function(mmlNodes) {
- for (var i = 0, mml; mml = mmlNodes[i]; i++) {
- if (this.mathml.indexOf(mml) == -1) {
- this.mathml.push(mml);
- }
- }
-};
-
-
-/**
- * Removes MathML nodes from the node's store of MathML nodes.
- * @param {Array<Node>} mmlNodes List of MathML nodes.
- * @private
- */
-cvox.SemanticTree.Node.prototype.removeMathmlNodes_ = function(mmlNodes) {
- var mmlList = this.mathml;
- for (var i = 0, mml; mml = mmlNodes[i]; i++) {
- var index = mmlList.indexOf(mml);
- if (index != -1) {
- mmlList.splice(index, 1);
- }
- }
- this.mathml = mmlList;
-};
-
-
-/**
- * Appends a child to the node.
- * @param {cvox.SemanticTree.Node} child The new child.
- * @private
- */
-cvox.SemanticTree.Node.prototype.appendChild_ = function(child) {
- this.childNodes.push(child);
- this.addMathmlNodes_(child.mathml);
- child.parent = this;
-};
-
-
-/**
- * Replaces a child node of the node.
- * @param {!cvox.SemanticTree.Node} oldNode The node to be replaced.
- * @param {!cvox.SemanticTree.Node} newNode The new node.
- * @private
- */
-cvox.SemanticTree.Node.prototype.replaceChild_ = function(oldNode, newNode) {
- var index = this.childNodes.indexOf(oldNode);
- if (index == -1) {
- return;
- }
- newNode.parent = this;
- oldNode.parent = null;
- this.childNodes[index] = newNode;
- // To not mess up the order of MathML elements more than necessary, we only
- // remove and add difference lists. The hope is that we might end up with
- // little change.
- var removeMathml = oldNode.mathml.filter(
- function(x) {return newNode.mathml.indexOf(x) == -1;});
- var addMathml = newNode.mathml.filter(
- function(x) {return oldNode.mathml.indexOf(x) == -1;});
- this.removeMathmlNodes_(removeMathml);
- this.addMathmlNodes_(addMathml);
-};
-
-
-/**
- * Appends a content node to the node.
- * @param {cvox.SemanticTree.Node} node The new content node.
- * @private
- */
-cvox.SemanticTree.Node.prototype.appendContentNode_ = function(node) {
- if (node) {
- this.contentNodes.push(node);
- this.addMathmlNodes_(node.mathml);
- node.parent = this;
- }
-};
-
-
-/**
- * Removes a content node from the node.
- * @param {cvox.SemanticTree.Node} node The content node to be removed.
- * @private
- */
-cvox.SemanticTree.Node.prototype.removeContentNode_ = function(node) {
- if (node) {
- var index = this.contentNodes.indexOf(node);
- if (index != -1) {
- this.contentNodes.splice(index, 1);
- }
- }
-};
-
-
-/**
- * This is the main function that creates the semantic tree by recursively
- * parsing the initial MathML tree and bottom up assembling the tree.
- * @param {!Element} mml The MathML tree.
- * @return {!cvox.SemanticTree.Node} The root of the new tree.
- * @private
- */
-cvox.SemanticTree.prototype.parseMathml_ = function(mml) {
- var children = cvox.DomUtil.toArray(mml.children);
- switch (cvox.SemanticUtil.tagName(mml)) {
- case 'MATH':
- case 'MROW':
- case 'MPADDED':
- case 'MSTYLE':
- children = cvox.SemanticUtil.purgeNodes(children);
- // Single child node, i.e. the row is meaningless.
- if (children.length == 1) {
- return this.parseMathml_(/** @type {!Element} */(children[0]));
- }
- // Case of a 'meaningful' row, even if they are empty.
- return this.processRow_(this.parseMathmlChildren_(children));
- break;
- case 'MFRAC':
- var newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.FRACTION,
- [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
- []);
- newNode.role = cvox.SemanticAttr.Role.DIVISION;
- return newNode;
- break;
- case 'MSUB':
- case 'MSUP':
- case 'MSUBSUP':
- case 'MOVER':
- case 'MUNDER':
- case 'MUNDEROVER':
- return this.makeLimitNode_(cvox.SemanticUtil.tagName(mml),
- this.parseMathmlChildren_(children));
- break;
- case 'MROOT':
- return this.makeBranchNode_(
- cvox.SemanticAttr.Type.ROOT,
- [this.parseMathml_(children[0]), this.parseMathml_(children[1])],
- []);
- break;
- case 'MSQRT':
- children = this.parseMathmlChildren_(
- cvox.SemanticUtil.purgeNodes(children));
- return this.makeBranchNode_(
- cvox.SemanticAttr.Type.SQRT, [this.processRow_(children)], []);
- break;
- case 'MTABLE':
- newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.TABLE,
- this.parseMathmlChildren_(children), []);
- if (cvox.SemanticTree.tableIsMultiline_(newNode)) {
- this.tableToMultiline_(newNode);
- }
- return newNode;
- break;
- case 'MTR':
- newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.ROW,
- this.parseMathmlChildren_(children), []);
- newNode.role = cvox.SemanticAttr.Role.TABLE;
- return newNode;
- break;
- case 'MTD':
- children = this.parseMathmlChildren_(
- cvox.SemanticUtil.purgeNodes(children));
- newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.CELL, [this.processRow_(children)], []);
- newNode.role = cvox.SemanticAttr.Role.TABLE;
- return newNode;
- break;
- case 'MTEXT':
- var leaf = this.makeLeafNode_(mml);
- leaf.type = cvox.SemanticAttr.Type.TEXT;
- return leaf;
- break;
- // TODO (sorge) Role and font of multi-character and digits unicode strings.
- // TODO (sorge) Reclassify wrongly tagged numbers or identifiers.
- // TODO (sorge) Put this all in a single clean reclassification method.
- case 'MI':
- leaf = this.makeLeafNode_(mml);
- if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
- leaf.type = cvox.SemanticAttr.Type.IDENTIFIER;
- }
- return leaf;
- break;
- case 'MN':
- leaf = this.makeLeafNode_(mml);
- if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
- leaf.type = cvox.SemanticAttr.Type.NUMBER;
- }
- return leaf;
- break;
- case 'MO':
- leaf = this.makeLeafNode_(mml);
- if (leaf.type == cvox.SemanticAttr.Type.UNKNOWN) {
- leaf.type = cvox.SemanticAttr.Type.OPERATOR;
- }
- return leaf;
- break;
- // TODO (sorge) Do something useful with error and phantom symbols.
- default:
- // Ordinarilly at this point we should not get any other tag.
- return this.makeUnprocessed_(mml);
- }
-};
-
-
-/**
- * Parse a list of MathML nodes into the semantic tree.
- * @param {Array<Element>} mmls A list of MathML nodes.
- * @return {!Array<cvox.SemanticTree.Node>} The list of resulting semantic
- * node.
- * @private
- */
-cvox.SemanticTree.prototype.parseMathmlChildren_ = function(mmls) {
- var result = [];
- for (var i = 0, mml; mml = mmls[i]; i++) {
- result.push(this.parseMathml_(mml));
- }
- return result;
-};
-
-/**
- * Create a node that is to be processed at a later point in time.
- * @param {Node} mml The MathML tree.
- * @return {!cvox.SemanticTree.Node} The new node.
- * @private
- */
-cvox.SemanticTree.prototype.makeUnprocessed_ = function(mml) {
- var node = this.createNode_();
- node.mathml = [mml];
- return node;
-};
-
-
-/**
- * Create an empty leaf node.
- * @return {!cvox.SemanticTree.Node} The new node.
- * @private
- */
-cvox.SemanticTree.prototype.makeEmptyNode_ = function() {
- var node = this.createNode_();
- node.type = cvox.SemanticAttr.Type.EMPTY;
- return node;
-};
-
-
-/**
- * Create a leaf node.
- * @param {Node} mml The MathML tree.
- * @return {!cvox.SemanticTree.Node} The new node.
- * @private
- */
-cvox.SemanticTree.prototype.makeLeafNode_ = function(mml) {
- var node = this.createNode_();
- node.mathml = [mml];
- node.updateContent_(mml.textContent);
- node.font = mml.getAttribute('mathvariant') || node.font;
- return node;
-};
-
-
-/**
- * Create a branching node.
- * @param {!cvox.SemanticAttr.Type} type The type of the node.
- * @param {!Array<cvox.SemanticTree.Node>} children The child nodes.
- * @param {!Array<cvox.SemanticTree.Node>} contentNodes The content Nodes.
- * @param {string=} content Content string if there is any.
- * @return {!cvox.SemanticTree.Node} The new node.
- * @private
- */
-cvox.SemanticTree.prototype.makeBranchNode_ = function(
- type, children, contentNodes, content) {
- var node = this.createNode_();
- if (content) {
- node.updateContent_(content);
- }
- node.type = type;
- node.childNodes = children;
- node.contentNodes = contentNodes;
- children.concat(contentNodes)
- .forEach(
- function(x) {
- x.parent = node;
- node.addMathmlNodes_(x.mathml);
- });
- return node;
-};
-
-
-/**
- * Create a branching node for an implicit operation, currently assumed to
- * be of multiplicative type.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes The operands.
- * @return {!cvox.SemanticTree.Node} The new branch node.
- * @private
- */
-cvox.SemanticTree.prototype.makeImplicitNode_ = function(nodes) {
- if (nodes.length == 1) {
- return nodes[0];
- }
- var operator = this.createNode_();
- // For now we assume this is a multiplication using invisible times.
- operator.updateContent_(cvox.SemanticAttr.invisibleTimes());
- var newNode = this.makeInfixNode_(nodes, operator);
- newNode.role = cvox.SemanticAttr.Role.IMPLICIT;
- return newNode;
-};
-
-
-/**
- * Create a branching node for an infix operation.
- * @param {!Array<cvox.SemanticTree.Node>} children The operands.
- * @param {!cvox.SemanticTree.Node} opNode The operator.
- * @return {!cvox.SemanticTree.Node} The new branch node.
- * @private
- */
-cvox.SemanticTree.prototype.makeInfixNode_ = function(children, opNode) {
- return this.makeBranchNode_(
- cvox.SemanticAttr.Type.INFIXOP, children, [opNode], opNode.textContent);
-};
-
-
-/**
- * Creates a node of the specified type by collapsing the given node list into
- * one content (thereby concatenating the content of each node into a single
- * content string) with the inner node as a child.
- * @param {!cvox.SemanticTree.Node} inner The inner node.
- * @param {!Array<cvox.SemanticTree.Node>} nodeList List of nodes.
- * @param {!cvox.SemanticAttr.Type} type The new type of the node.
- * @return {!cvox.SemanticTree.Node} The new branch node.
- * @private
- */
-cvox.SemanticTree.prototype.makeConcatNode_ = function(inner, nodeList, type) {
- if (nodeList.length == 0) {
- return inner;
- }
- var content = nodeList.map(function(x) {return x.textContent;}).join(' ');
- var newNode = this.makeBranchNode_(type, [inner], nodeList, content);
- if (nodeList.length > 0) {
- newNode.role = cvox.SemanticAttr.Role.MULTIOP;
- }
- return newNode;
-};
-
-
-/**
- * Wraps a node into prefix operators.
- * Example: + - a becomes (+ (- (a)))
- * Input: a [+, -] -> Output: content: '+ -', child: a
- * @param {!cvox.SemanticTree.Node} node The inner node.
- * @param {!Array<cvox.SemanticTree.Node>} prefixes Prefix operators
- * from the outermost to the innermost.
- * @return {!cvox.SemanticTree.Node} The new branch node.
- * @private
- */
-cvox.SemanticTree.prototype.makePrefixNode_ = function(node, prefixes) {
- var negatives = cvox.SemanticTree.partitionNodes_(
- prefixes, cvox.SemanticTree.attrPred_('role', 'SUBTRACTION'));
- var newNode = this.makeConcatNode_(
- node, negatives.comp.pop(), cvox.SemanticAttr.Type.PREFIXOP);
-
- while (negatives.rel.length > 0) {
- newNode = this.makeConcatNode_(
- newNode, [negatives.rel.pop()], cvox.SemanticAttr.Type.PREFIXOP);
- newNode.role = cvox.SemanticAttr.Role.NEGATIVE;
- newNode = this.makeConcatNode_(
- newNode, negatives.comp.pop(), cvox.SemanticAttr.Type.PREFIXOP);
- }
- return newNode;
-};
-
-
-/**
- * Wraps a node into postfix operators.
- * Example: a - + becomes (((a) -) +)
- * Input: a [-, +] -> Output: content: '- +', child: a
- * @param {!cvox.SemanticTree.Node} node The inner node.
- * @param {!Array<cvox.SemanticTree.Node>} postfixes Postfix operators from
- * innermost to outermost.
- * @return {!cvox.SemanticTree.Node} The new branch node.
- * @private
- */
-cvox.SemanticTree.prototype.makePostfixNode_ = function(node, postfixes) {
- return this.makeConcatNode_(
- node, postfixes, cvox.SemanticAttr.Type.POSTFIXOP);
-};
-
-
-// TODO (sorge) Separate out interspersed text before the relations in row
-// heuristic otherwise we get them as implicit operations!
-// Currently we handle that later in the rules, which is rather messy.
-/**
- * Processes a list of nodes, combining expressions by delimiters, tables,
- * punctuation sequences, function/big operator/integral applications to
- * generate a syntax tree with relation and operator precedence.
- *
- * This is the main heuristic to rewrite a flat row of terms into a meaningful
- * term tree.
- * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
- * @private
- */
-cvox.SemanticTree.prototype.processRow_ = function(nodes) {
- if (nodes.length == 0) {
- return this.makeEmptyNode_();
- }
- nodes = this.getFencesInRow_(nodes);
- nodes = this.processTablesInRow_(nodes);
- nodes = this.getPunctuationInRow_(nodes);
- nodes = this.getFunctionsInRow_(nodes);
- return this.processRelationsInRow_(nodes);
-};
-
-
-/**
- * Constructs a syntax tree with relation and operator precedence from a list
- * of nodes.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
- * @private
- */
-cvox.SemanticTree.prototype.processRelationsInRow_ = function(nodes) {
- var partition = cvox.SemanticTree.partitionNodes_(
- nodes, cvox.SemanticTree.attrPred_('type', 'RELATION'));
- var firstRel = partition.rel[0];
-
- if (!firstRel) {
- return this.processOperationsInRow_(nodes);
- }
- if (nodes.length == 1) {
- return nodes[0];
- }
- var children = partition.comp.map(
- goog.bind(this.processOperationsInRow_, this));
- if (partition.rel.every(
- function(x) {return x.textContent == firstRel.textContent;})) {
- return this.makeBranchNode_(
- cvox.SemanticAttr.Type.RELSEQ, children, partition.rel,
- firstRel.textContent);
- }
- return this.makeBranchNode_(
- cvox.SemanticAttr.Type.MULTIREL, children, partition.rel);
-};
-
-
-/**
- * Constructs a syntax tree with operator precedence from a list nodes.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
- * @private
- */
-cvox.SemanticTree.prototype.processOperationsInRow_ = function(nodes) {
- if (nodes.length == 0) {
- return this.makeEmptyNode_();
- }
- if (nodes.length == 1) {
- return nodes[0];
- }
-
- var prefix = [];
- while (nodes.length > 0 &&
- nodes[0].type == cvox.SemanticAttr.Type.OPERATOR) {
- prefix.push(nodes.shift());
- }
- // Pathological case: only operators in row.
- if (nodes.length == 0) {
- return this.makePrefixNode_(prefix.pop(), prefix);
- }
- if (nodes.length == 1) {
- return this.makePrefixNode_(nodes[0], prefix);
- }
-
- var split = cvox.SemanticTree.sliceNodes_(
- nodes, cvox.SemanticTree.attrPred_('type', 'OPERATOR'));
- // At this point, we know that split.head is not empty!
- var node = this.makePrefixNode_(
- this.makeImplicitNode_(
- /** @type {!Array<!cvox.SemanticTree.Node>} */ (split.head)),
- prefix);
- if (!split.div) {
- return node;
- }
- return this.makeOperationsTree_(split.tail, node, split.div);
-};
-
-
-/**
- * Recursively constructs syntax tree with operator precedence from a list nodes
- * given a initial root node.
- * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
- * @param {!cvox.SemanticTree.Node} root Initial tree.
- * @param {!cvox.SemanticTree.Node} lastop Last operator that has not been
- * processed yet.
- * @param {Array<cvox.SemanticTree.Node>=} prefixes Operator nodes that will
- * become prefix operation (or postfix in case they come after last operand).
- * @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
- * @private
- */
-cvox.SemanticTree.prototype.makeOperationsTree_ = function(
- nodes, root, lastop, prefixes) {
- prefixes = prefixes || [];
-
- if (nodes.length == 0) {
- // Left over prefixes become postfixes.
- prefixes.unshift(lastop);
- if (root.type == cvox.SemanticAttr.Type.INFIXOP) {
- // We assume prefixes bind stronger than postfixes.
- var node = this.makePostfixNode_(
- // Here we know that the childNodes are not empty!
- /** @type {!cvox.SemanticTree.Node} */ (root.childNodes.pop()),
- prefixes);
- root.appendChild_(node);
- return root;
- }
- return this.makePostfixNode_(root, prefixes);
- }
-
- var split = cvox.SemanticTree.sliceNodes_(
- nodes, cvox.SemanticTree.attrPred_('type', 'OPERATOR'));
-
- if (split.head.length == 0) {
- prefixes.push(split.div);
- return this.makeOperationsTree_(split.tail, root, lastop, prefixes);
- }
-
- var node = this.makePrefixNode_(
- this.makeImplicitNode_(split.head), prefixes);
- var newNode = this.appendOperand_(root, lastop, node);
- if (!split.div) {
- return newNode;
- }
-
- return this.makeOperationsTree_(split.tail, newNode, split.div, []);
-};
-
-// TODO (sorge) The following four functions could be combined into
-// a single one. Currently it is clearer the way it is, though.
-/**
- * Appends an operand at the right place in an operator tree.
- * @param {!cvox.SemanticTree.Node} root The operator tree.
- * @param {!cvox.SemanticTree.Node} op The operator node.
- * @param {!cvox.SemanticTree.Node} node The node to be added.
- * @return {!cvox.SemanticTree.Node} The modified root node.
- * @private
- */
-cvox.SemanticTree.prototype.appendOperand_ = function(root, op, node) {
- // In general our operator tree will have the form that additions and
- // subtractions are stacked, while multiplications are subordinate.
- if (root.type != cvox.SemanticAttr.Type.INFIXOP) {
- return this.makeInfixNode_([root, node], op);
- }
- if (this.appendExistingOperator_(root, op, node)) {
- return root;
- }
- return op.role == cvox.SemanticAttr.Role.MULTIPLICATION ?
- this.appendMultiplicativeOp_(root, op, node) :
- this.appendAdditiveOp_(root, op, node);
-};
-
-
-/**
- * Appends a multiplicative operator and operand.
- * @param {!cvox.SemanticTree.Node} root The root node.
- * @param {!cvox.SemanticTree.Node} op The operator node.
- * @param {!cvox.SemanticTree.Node} node The operand node to be added.
- * @return {!cvox.SemanticTree.Node} The modified root node.
- * @private
- */
-cvox.SemanticTree.prototype.appendMultiplicativeOp_ = function(root, op, node) {
- var lastRoot = root;
- var lastChild = root.childNodes[root.childNodes.length - 1];
- while (lastChild && lastChild.type == cvox.SemanticAttr.Type.INFIXOP) {
- lastRoot = lastChild;
- lastChild = lastRoot.childNodes[root.childNodes.length - 1];
- }
- var newNode = this.makeInfixNode_([lastRoot.childNodes.pop(), node], op);
- lastRoot.appendChild_(newNode);
- return root;
-};
-
-
-/**
- * Appends an additive/substractive operator and operand.
- * @param {!cvox.SemanticTree.Node} root The old root node.
- * @param {!cvox.SemanticTree.Node} op The operator node.
- * @param {!cvox.SemanticTree.Node} node The operand node to be added.
- * @return {!cvox.SemanticTree.Node} The new root node.
- * @private
- */
-cvox.SemanticTree.prototype.appendAdditiveOp_ = function(root, op, node) {
- return this.makeInfixNode_([root, node], op);
-};
-
-
-/**
- * Adds an operand to an operator node if it is the continuation of an existing
- * operation.
- * @param {!cvox.SemanticTree.Node} root The root node.
- * @param {!cvox.SemanticTree.Node} op The operator node.
- * @param {!cvox.SemanticTree.Node} node The operand node to be added.
- * @return {boolean} True if operator was successfully appended.
- * @private
- */
-cvox.SemanticTree.prototype.appendExistingOperator_ = function(root, op, node) {
- if (!root || root.type != cvox.SemanticAttr.Type.INFIXOP) {
- return false;
- }
- if (root.textContent == op.textContent) {
- root.appendContentNode_(op);
- root.appendChild_(node);
- return true;
- }
- this.appendExistingOperator_(
- // Again, if this is an INFIXOP node, we know it has a child!
- /** @type {!cvox.SemanticTree.Node} */
- (root.childNodes[root.childNodes.length - 1]),
- op, node);
-};
-
-
-// TODO (sorge) The following procedure needs a rational reconstruction. It
-// contains a number of similar cases which should be combined.
-/**
- * Combines delimited expressions in a list of nodes.
- *
- * The basic idea of the heuristic is as follows:
- * 1. Opening and closing delimiters are matched regardless of the actual shape
- * of the fence. These are turned into fenced nodes.
- * 2. Neutral fences are matched only with neutral fences of the same shape.
- * 3. For a collection of unmatched neutral fences we try to get a maximum
- * number of matching fences. E.g. || a|b || would be turned into a fenced
- * node with fences || and content a|b.
- * 4. Any remaining unmatched delimiters are turned into punctuation nodes.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!Array<!cvox.SemanticTree.Node>} The new list of nodes.
- * @private
- */
-cvox.SemanticTree.prototype.getFencesInRow_ = function(nodes) {
- var partition = cvox.SemanticTree.partitionNodes_(
- nodes, cvox.SemanticTree.attrPred_('type', 'FENCE'));
- var felem = partition.comp.shift();
- return this.processFences_(partition.rel, partition.comp, [], [felem]);
-};
-
-
-/**
- * Recursively processes a list of nodes and combines all the fenced expressions
- * into single nodes. It also processes singular fences, building expressions
- * that are only fenced left or right.
- * @param {!Array<cvox.SemanticTree.Node>} fences FIFO queue of fence nodes.
- * @param {!Array<Array<cvox.SemanticTree.Node>>} content FIFO queue content
- * between fences.
- * @param {!Array<cvox.SemanticTree.Node>} openStack LIFO stack of open fences.
- * @param {!Array<!Array<cvox.SemanticTree.Node>>} contentStack LIFO stack of
- * content between fences yet to be processed.
- * @return {!Array<cvox.SemanticTree.Node>} A list of nodes with all fenced
- * expressions processed.
- * @private
- */
-cvox.SemanticTree.prototype.processFences_ = function(
- fences, content, openStack, contentStack) {
- // Base case 1: Everything is used up.
- if (fences.length == 0 && openStack.length == 0) {
- return contentStack[0];
- }
- var openPred = cvox.SemanticTree.attrPred_('role', 'OPEN');
- // Base case 2: Only open and neutral fences are left on the stack.
- if (fences.length == 0) {
- // Basic idea:
- // - make punctuation nodes from open fences
- // - combine as many neutral fences as possible, if the are not separated by
- // open fences.
- // The idea is to allow for things like case statements etc. and not bury
- // them inside a neutral fenced expression.
- //
- // 0. We process the list from left to right. Hence the first element on the
- // content stack are actually left most elements in the expression.
- // 1. Slice at open fence.
- // 2. On tail optimize for neutral fences.
- // 3. Repeat until fence stack is exhausted.
- // Push rightmost elements onto the result.
- var result = contentStack.shift();
- while (openStack.length > 0) {
- if (openPred(openStack[0])) {
- var firstOpen = openStack.shift();
- cvox.SemanticTree.fenceToPunct_(firstOpen);
- result.push(firstOpen);
- } else {
- var split = cvox.SemanticTree.sliceNodes_(openStack, openPred);
- var cutLength = split.head.length - 1;
- var innerNodes = this.processNeutralFences_(
- split.head, contentStack.slice(0, cutLength));
- contentStack = contentStack.slice(cutLength);
- //var rightContent = contentStack.shift();
- result.push.apply(result, innerNodes);
- //result.push.apply(result, rightContent);
- if (split.div) {
- split.tail.unshift(split.div);
- }
- openStack = split.tail;
- }
- result.push.apply(result, contentStack.shift());
- }
- return result;
- }
- var lastOpen = openStack[openStack.length - 1];
- var firstRole = fences[0].role;
- // General opening case.
- // Either we have an open fence.
- if (firstRole == cvox.SemanticAttr.Role.OPEN ||
- // Or we have a neutral fence that does not have a counter part.
- (firstRole == cvox.SemanticAttr.Role.NEUTRAL &&
- (!lastOpen ||
- fences[0].textContent != lastOpen.textContent))) {
- openStack.push(fences.shift());
- contentStack.push(content.shift());
- return this.processFences_(fences, content, openStack, contentStack);
- }
- // General closing case.
- if (lastOpen && (
- // Closing fence for some opening fence.
- (firstRole == cvox.SemanticAttr.Role.CLOSE &&
- lastOpen.role == cvox.SemanticAttr.Role.OPEN) ||
- // Netural fence with exact counter part.
- (firstRole == cvox.SemanticAttr.Role.NEUTRAL &&
- fences[0].textContent == lastOpen.textContent))) {
- var fenced = this.makeHorizontalFencedNode_(
- openStack.pop(), fences.shift(), contentStack.pop());
- contentStack.push(contentStack.pop().concat([fenced], content.shift()));
- return this.processFences_(fences, content, openStack, contentStack);
- }
- // Closing with a neutral fence on the stack.
- if (lastOpen && firstRole == cvox.SemanticAttr.Role.CLOSE &&
- lastOpen.role == cvox.SemanticAttr.Role.NEUTRAL &&
- openStack.some(openPred)) {
- // Steps of the algorithm:
- // 1. Split list at right most opening bracket.
- // 2. Cut content list at corresponding length.
- // 3. Optimise the neutral fences.
- // 4. Make fenced node.
- //
- // Careful, this reverses openStack!
- var split = cvox.SemanticTree.sliceNodes_(openStack, openPred, true);
- // We know that
- // (a) div & tail exist,
- // (b) all are combined in this step into a single fenced node,
- // (c) head is the new openStack,
- // (d) the new contentStack is remainder of contentStack + new fenced node +
- // shift of content.
- var rightContent = contentStack.pop();
- var cutLength = contentStack.length - split.tail.length + 1;
- var innerNodes = this.processNeutralFences_(
- split.tail, contentStack.slice(cutLength));
- contentStack = contentStack.slice(0, cutLength);
- var fenced = this.makeHorizontalFencedNode_(
- split.div, fences.shift(),
- contentStack.pop().concat(innerNodes, rightContent));
- contentStack.push(contentStack.pop().concat([fenced], content.shift()));
- return this.processFences_(fences, content, split.head, contentStack);
- }
- // Final Case: A singular closing fence.
- // We turn the fence into a punctuation.
- var fenced = fences.shift();
- cvox.SemanticTree.fenceToPunct_(fenced);
- contentStack.push(contentStack.pop().concat([fenced], content.shift()));
- return this.processFences_(fences, content, openStack, contentStack);
-};
-
-
-// TODO (sorge) The following could be done with linear programming.
-/**
- * Trys to combine neutral fences as much as possible.
- * @param {!Array<!cvox.SemanticTree.Node>} fences A list of neutral fences.
- * @param {!Array<!Array<cvox.SemanticTree.Node>>} content Intermediate
- * content. Observe that |content| = |fences| - 1
- * @return {!Array<cvox.SemanticTree.Node>} List of node with fully fenced
- * nodes.
- * @private
- */
-cvox.SemanticTree.prototype.processNeutralFences_ = function(fences, content) {
- if (fences.length == 0) {
- return fences;
- }
- if (fences.length == 1) {
- cvox.SemanticTree.fenceToPunct_(fences[0]);
- return fences;
- }
- var firstFence = fences.shift();
- var split = cvox.SemanticTree.sliceNodes_(
- fences, function(x) {return x.textContent == firstFence.textContent;});
- if (!split.div) {
- cvox.SemanticTree.fenceToPunct_(firstFence);
- var restContent = content.shift();
- restContent.unshift(firstFence);
- return restContent.concat(this.processNeutralFences_(fences, content));
- }
- var newContent = this.combineFencedContent_(
- firstFence, split.div, split.head, content);
- if (split.tail.length > 0) {
- var leftContent = newContent.shift();
- var result = this.processNeutralFences_(split.tail, newContent);
- return leftContent.concat(result);
- }
- return newContent[0];
-};
-
-
-/**
- * Combines nodes framed by two matching fences using the given content.
- * Example: leftFence: [, rightFence: ], midFences: |, |
- * content: c1, c2, c3, c4, ... cn
- * return: [c1 | c2 | c3 ], c4, ... cn
- * @param {!cvox.SemanticTree.Node} leftFence The left fence.
- * @param {!cvox.SemanticTree.Node} rightFence The right fence.
- * @param {!Array<cvox.SemanticTree.Node>} midFences A list of intermediate
- * fences.
- * @param {!Array<!Array<cvox.SemanticTree.Node>>} content Intermediate
- * content. Observe that |content| = |fences| - 1 + k where k >= 0 is the
- * remainder.
- * @return {!Array<!Array<cvox.SemanticTree.Node>>} List of content nodes
- * where the first is the fully fenced node wrt. the given left and right
- * fence.
- * @private
- */
-cvox.SemanticTree.prototype.combineFencedContent_ = function(
- leftFence, rightFence, midFences, content) {
-
- if (midFences.length == 0) {
- var fenced = this.makeHorizontalFencedNode_(
- leftFence, rightFence, content.shift());
- content.unshift(fenced);
- return content;
- }
-
- var leftContent = content.shift();
- var cutLength = midFences.length - 1;
- var midContent = content.slice(0, cutLength);
- content = content.slice(cutLength);
- var rightContent = content.shift();
- var innerNodes = this.processNeutralFences_(midFences, midContent);
- leftContent.push.apply(leftContent, innerNodes);
- leftContent.push.apply(leftContent, rightContent);
- var fenced = this.makeHorizontalFencedNode_(
- leftFence, rightFence, leftContent);
- if (content.length > 0) {
- content[0].unshift(fenced);
- } else {
- content = [[fenced]];
- }
- return content;
- };
-
-
-/**
- * Rewrite fences into punctuation. This is done with any "leftover" fence.
- * @param {cvox.SemanticTree.Node} fence Fence.
- * @private
- */
-cvox.SemanticTree.fenceToPunct_ = function(fence) {
- fence.type = cvox.SemanticAttr.Type.PUNCTUATION;
- switch (fence.role) {
- case cvox.SemanticAttr.Role.NEUTRAL:
- fence.role = cvox.SemanticAttr.Role.VBAR;
- break;
- case cvox.SemanticAttr.Role.OPEN:
- fence.role = cvox.SemanticAttr.Role.OPENFENCE;
- break;
- case cvox.SemanticAttr.Role.CLOSE:
- fence.role = cvox.SemanticAttr.Role.CLOSEFENCE;
- break;
- }
-};
-
-
-/**
- * Create a fenced node.
- * @param {cvox.SemanticTree.Node} ofence Opening fence.
- * @param {cvox.SemanticTree.Node} cfence Closing fence.
- * @param {!Array<cvox.SemanticTree.Node>} content The content
- * between the fences.
- * @return {!cvox.SemanticTree.Node} The new node.
- * @private
- */
-cvox.SemanticTree.prototype.makeHorizontalFencedNode_ = function(
- ofence, cfence, content) {
- var childNode = this.processRow_(content);
- var newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.FENCED, [childNode], [ofence, cfence]);
- if (ofence.role == cvox.SemanticAttr.Role.OPEN) {
- newNode.role = cvox.SemanticAttr.Role.LEFTRIGHT;
- } else {
- newNode.role = ofence.role;
- }
- return newNode;
-};
-
-
-/**
- * Combines sequences of punctuated expressions in a list of nodes.
- * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!Array<cvox.SemanticTree.Node>} The new list of nodes.
- * @private
- */
-cvox.SemanticTree.prototype.getPunctuationInRow_ = function(nodes) {
- // For now we just make a punctuation node with a particular role. This is
- // similar to an mrow. The only exception are ellipses, which we assume to be
- // in lieu of identifiers.
- // In addition we keep the single punctuation nodes as content.
- var partition = cvox.SemanticTree.partitionNodes_(
- nodes, function(x) {
- return cvox.SemanticTree.attrPred_('type', 'PUNCTUATION')(x) &&
- !cvox.SemanticTree.attrPred_('role', 'ELLIPSIS')(x);});
- if (partition.rel.length == 0) {
- return nodes;
- }
- var newNodes = [];
- var firstComp = partition.comp.shift();
- if (firstComp.length > 0) {
- newNodes.push(this.processRow_(firstComp));
- }
- var relCounter = 0;
- while (partition.comp.length > 0) {
- newNodes.push(partition.rel[relCounter++]);
- firstComp = partition.comp.shift();
- if (firstComp.length > 0) {
- newNodes.push(this.processRow_(firstComp));
- }
- }
- return [this.makePunctuatedNode_(newNodes, partition.rel)];
-};
-
-
-/**
- * Create a punctuated node.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes List of all nodes separated
- * by punctuations.
- * @param {!Array<!cvox.SemanticTree.Node>} punctuations List of all separating
- * punctations. Observe that punctations is a subset of nodes.
- * @return {!cvox.SemanticTree.Node}
- * @private
- */
-cvox.SemanticTree.prototype.makePunctuatedNode_ = function(
- nodes, punctuations) {
- var newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.PUNCTUATED, nodes, punctuations);
-
- if (punctuations.length == 1 &&
- nodes[0].type == cvox.SemanticAttr.Type.PUNCTUATION) {
- newNode.role = cvox.SemanticAttr.Role.STARTPUNCT;
- } else if (punctuations.length == 1 &&
- nodes[nodes.length - 1].type == cvox.SemanticAttr.Type.PUNCTUATION) {
- newNode.role = cvox.SemanticAttr.Role.ENDPUNCT;
- } else {
- newNode.role = cvox.SemanticAttr.Role.SEQUENCE;
- }
- return newNode;
-};
-
-
-/**
- * Creates a limit node from a sub/superscript or over/under node if the central
- * element is a big operator. Otherwise it creates the standard elements.
- * @param {string} mmlTag The tag name of the original node.
- * @param {!Array<!cvox.SemanticTree.Node>} children The children of the
- * original node.
- * @return {!cvox.SemanticTree.Node} The newly created limit node.
- * @private
- */
-cvox.SemanticTree.prototype.makeLimitNode_ = function(mmlTag, children) {
- var center = children[0];
- var isFunction = cvox.SemanticTree.attrPred_('type', 'FUNCTION')(center);
- // TODO (sorge) Put this into a single function.
- var isLimit = cvox.SemanticTree.attrPred_('type', 'LARGEOP')(center) ||
- cvox.SemanticTree.attrPred_('type', 'LIMBOTH')(center) ||
- cvox.SemanticTree.attrPred_('type', 'LIMLOWER')(center) ||
- cvox.SemanticTree.attrPred_('type', 'LIMUPPER')(center) ||
- (isFunction && cvox.SemanticTree.attrPred_('role', 'LIMFUNC')(center));
- var type = cvox.SemanticAttr.Type.UNKNOWN;
- // TODO (sorge) Make use of the difference in information on sub vs under etc.
- if (isLimit) {
- switch (mmlTag) {
- case 'MSUB':
- case 'MUNDER':
- type = cvox.SemanticAttr.Type.LIMLOWER;
- break;
- case 'MSUP':
- case 'MOVER':
- type = cvox.SemanticAttr.Type.LIMUPPER;
- break;
- case 'MSUBSUP':
- case 'MUNDEROVER':
- type = cvox.SemanticAttr.Type.LIMBOTH;
- break;
- }
- } else {
- switch (mmlTag) {
- case 'MSUB':
- type = cvox.SemanticAttr.Type.SUBSCRIPT;
- break;
- case 'MSUP':
- type = cvox.SemanticAttr.Type.SUPERSCRIPT;
- break;
- case 'MSUBSUP':
- var innerNode = this.makeBranchNode_(cvox.SemanticAttr.Type.SUBSCRIPT,
- [center, children[1]], []);
- innerNode.role = center.role;
- children = [innerNode, children[2]];
- type = cvox.SemanticAttr.Type.SUPERSCRIPT;
- break;
- case 'MOVER':
- type = cvox.SemanticAttr.Type.OVERSCORE;
- break;
- case 'MUNDER':
- type = cvox.SemanticAttr.Type.UNDERSCORE;
- break;
- case 'MUNDEROVER':
- default:
- var innerNode = this.makeBranchNode_(cvox.SemanticAttr.Type.UNDERSCORE,
- [center, children[1]], []);
- innerNode.role = center.role;
- children = [innerNode, children[2]];
- type = cvox.SemanticAttr.Type.OVERSCORE;
- break;
- }
- }
- var newNode = this.makeBranchNode_(type, children, []);
- newNode.role = center.role;
- return newNode;
-};
-
-
-/**
- * Recursive method to accumulate function expressions.
- *
- * The idea is to process functions in a row from left to right combining them
- * with there arguments. Thereby we take the notion of a function rather broadly
- * as a functional expressions that consists of a prefix and some arguments.
- * In particular we distinguish four types of functional expressions:
- * - integral: Integral expression.
- * - bigop: A big operator expression like a sum.
- * - prefix: A well defined prefix function such as sin, cos or a limit
- * functions like lim, max.
- * - simple: An expression consisting of letters that are potentially a function
- * symbol. If we have an explicit function application symbol
- * following the expression we turn into a prefix function. Otherwise
- * we decide heuristically if we could have a function application.
- * @param {!Array<cvox.SemanticTree.Node>} restNodes The remainder list of
- * nodes.
- * @param {!Array<cvox.SemanticTree.Node>=} result The result node list.
- * @return {!Array<!cvox.SemanticTree.Node>} The fully processed list.
- * @private
- */
-cvox.SemanticTree.prototype.getFunctionsInRow_ = function(restNodes, result) {
- result = result || [];
- // Base case.
- if (restNodes.length == 0) {
- return result;
- }
- var firstNode = /** @type {!cvox.SemanticTree.Node} */ (restNodes.shift());
- var heuristic = cvox.SemanticTree.classifyFunction_(firstNode, restNodes);
- // First node is not a function node.
- if (!heuristic) {
- result.push(firstNode);
- return this.getFunctionsInRow_(restNodes, result);
- }
- // Combine functions in the rest of the row.
- var processedRest = this.getFunctionsInRow_(restNodes, []);
- var newRest = this.getFunctionArgs_(firstNode, processedRest, heuristic);
- return result.concat(newRest);
-};
-
-
-/**
- * Classifies a function wrt. the heuristic that should be applied.
- * @param {!cvox.SemanticTree.Node} funcNode The node to be classified.
- * @param {!Array<cvox.SemanticTree.Node>} restNodes The remainder list of
- * nodes. They can useful to look ahead if there is an explicit function
- * application. If there is one, it will be destructively removed!
- * @return {!string} The string specifying the heuristic.
- * @private
- */
-cvox.SemanticTree.classifyFunction_ = function(funcNode, restNodes) {
- // We do not allow double function application. This is not lambda calculus!
- if (funcNode.type == cvox.SemanticAttr.Type.APPL ||
- funcNode.type == cvox.SemanticAttr.Type.BIGOP ||
- funcNode.type == cvox.SemanticAttr.Type.INTEGRAL) {
- return '';
- }
- // Find and remove explicit function applications.
- // We now treat funcNode as a prefix function, regardless of what its actual
- // content is.
- if (restNodes[0] &&
- restNodes[0].textContent == cvox.SemanticAttr.functionApplication()) {
- // Remove explicit function application. This is destructive on the
- // underlying list.
- restNodes.shift();
- cvox.SemanticTree.propagatePrefixFunc_(funcNode);
- return 'prefix';
- }
- switch (funcNode.role) {
- case cvox.SemanticAttr.Role.INTEGRAL:
- return 'integral';
- break;
- case cvox.SemanticAttr.Role.SUM:
- return 'bigop';
- break;
- case cvox.SemanticAttr.Role.PREFIXFUNC:
- case cvox.SemanticAttr.Role.LIMFUNC:
- return 'prefix';
- break;
- default:
- if (funcNode.type == cvox.SemanticAttr.Type.IDENTIFIER) {
- return 'simple';
- }
- }
- return '';
-};
-
-
-/**
- * Propagates a prefix function role in a node.
- * @param {cvox.SemanticTree.Node} funcNode The node whose role is to be
- * rewritten.
- * @private
- */
-cvox.SemanticTree.propagatePrefixFunc_ = function(funcNode) {
- if (funcNode) {
- funcNode.role = cvox.SemanticAttr.Role.PREFIXFUNC;
- cvox.SemanticTree.propagatePrefixFunc_(funcNode.childNodes[0]);
- }
-};
-
-
-/**
- * Computes the arguments for a function from a list of nodes depending on the
- * given heuristic.
- * @param {!cvox.SemanticTree.Node} func A function node.
- * @param {!Array<cvox.SemanticTree.Node>} rest List of nodes to choose
- * arguments from.
- * @param {string} heuristic The heuristic to follow.
- * @return {!Array<!cvox.SemanticTree.Node>} The function and the remainder of
- * the rest list.
- * @private
- */
-cvox.SemanticTree.prototype.getFunctionArgs_ = function(func, rest, heuristic) {
- switch (heuristic) {
- case 'integral':
- var components = this.getIntegralArgs_(rest);
- var integrand = this.processRow_(components.integrand);
- var funcNode = this.makeIntegralNode_(func, integrand, components.intvar);
- components.rest.unshift(funcNode);
- return components.rest;
- break;
- case 'prefix':
- if (rest[0] && rest[0].type == cvox.SemanticAttr.Type.FENCED) {
- funcNode = this.makeFunctionNode_(
- func, /** @type {!cvox.SemanticTree.Node} */ (rest.shift()));
- rest.unshift(funcNode);
- return rest;
- }
- case 'bigop':
- var partition = cvox.SemanticTree.sliceNodes_(
- rest, cvox.SemanticTree.prefixFunctionBoundary_);
- var arg = this.processRow_(partition.head);
- if (heuristic == 'prefix') {
- funcNode = this.makeFunctionNode_(func, arg);
- } else {
- funcNode = this.makeBigOpNode_(func, arg);
- }
- if (partition.div) {
- partition.tail.unshift(partition.div);
- }
- partition.tail.unshift(funcNode);
- return partition.tail;
- break;
- case 'simple':
- if (rest.length == 0) {
- return [func];
- }
- var firstArg = rest[0];
- if (firstArg.type == cvox.SemanticAttr.Type.FENCED &&
- firstArg.role != cvox.SemanticAttr.Role.NEUTRAL &&
- this.simpleFunctionHeuristic_(firstArg)) {
- funcNode = this.makeFunctionNode_(
- func, /** @type {!cvox.SemanticTree.Node} */ (rest.shift()));
- rest.unshift(funcNode);
- return rest;
- }
- rest.unshift(func);
- return rest;
- break;
- }
-};
-
-
-/**
- * Tail recursive function to obtain integral arguments.
- * @param {!Array<cvox.SemanticTree.Node>} nodes List of nodes to take
- * arguments from.
- * @param {Array<cvox.SemanticTree.Node>=} args List of integral arguments.
- * @return {{integrand: !Array<cvox.SemanticTree.Node>,
- * intvar: cvox.SemanticTree.Node,
- * rest: !Array<cvox.SemanticTree.Node>}}
- * Result split into integrand, integral variable and the remaining
- * elements.
- * @private
- */
-cvox.SemanticTree.prototype.getIntegralArgs_ = function(nodes, args) {
- args = args || [];
- if (nodes.length == 0) {
- return {integrand: args, intvar: null, rest: nodes};
- }
- var firstNode = nodes[0];
- if (cvox.SemanticTree.generalFunctionBoundary_(firstNode)) {
- return {integrand: args, intvar: null, rest: nodes};
- }
- if (cvox.SemanticTree.integralDxBoundarySingle_(firstNode)) {
- return {integrand: args, intvar: firstNode, rest: nodes.slice(1)};
- }
- if (nodes[1] && cvox.SemanticTree.integralDxBoundary_(firstNode, nodes[1])) {
- var comma = this.createNode_();
- comma.updateContent_(cvox.SemanticAttr.invisibleComma());
- var intvar = this.makePunctuatedNode_(
- [firstNode, comma, nodes[1]], [comma]);
- intvar.role = cvox.SemanticAttr.Role.INTEGRAL;
- return {integrand: args, intvar: intvar, rest: nodes.slice(2)};
- }
- args.push(nodes.shift());
- return this.getIntegralArgs_(nodes, args);
-};
-
-
-/**
- * Create a function node.
- * @param {!cvox.SemanticTree.Node} func The function operator.
- * @param {!cvox.SemanticTree.Node} arg The argument.
- * @return {!cvox.SemanticTree.Node} The new function node.
- * @private
- */
-cvox.SemanticTree.prototype.makeFunctionNode_ = function(func, arg) {
- var applNode = this.createNode_();
- applNode.updateContent_(cvox.SemanticAttr.functionApplication());
- applNode.type = cvox.SemanticAttr.Type.PUNCTUATION;
- applNode.role = cvox.SemanticAttr.Role.APPLICATION;
- var newNode = this.makeBranchNode_(cvox.SemanticAttr.Type.APPL, [func, arg],
- [applNode]);
- newNode.role = func.role;
- return newNode;
-};
-
-
-/**
- * Create a big operator node.
- * @param {!cvox.SemanticTree.Node} bigOp The big operator.
- * @param {!cvox.SemanticTree.Node} arg The argument.
- * @return {!cvox.SemanticTree.Node} The new big operator node.
- * @private
- */
-cvox.SemanticTree.prototype.makeBigOpNode_ = function(bigOp, arg) {
- var newNode = this.makeBranchNode_(
- cvox.SemanticAttr.Type.BIGOP, [bigOp, arg], []);
- newNode.role = bigOp.role;
- return newNode;
-};
-
-
-/**
- * Create an integral node. It has three children: integral, integrand and
- * integration variable. The latter two can be omitted.
- * @param {!cvox.SemanticTree.Node} integral The integral operator.
- * @param {cvox.SemanticTree.Node} integrand The integrand.
- * @param {cvox.SemanticTree.Node} intvar The integral variable.
- * @return {!cvox.SemanticTree.Node} The new integral node.
- * @private
- */
-cvox.SemanticTree.prototype.makeIntegralNode_ = function(
- integral, integrand, intvar) {
- integrand = integrand || this.makeEmptyNode_();
- intvar = intvar || this.makeEmptyNode_();
- var newNode = this.makeBranchNode_(cvox.SemanticAttr.Type.INTEGRAL,
- [integral, integrand, intvar], []);
- newNode.role = integral.role;
- return newNode;
-};
-
-
-/**
- * Predicate implementing the boundary criteria for simple functions:
- *
- * @param {!cvox.SemanticTree.Node} node A semantic node of type fenced.
- * @return {boolean} True if the node meets the boundary criteria.
- * @private
- */
-cvox.SemanticTree.prototype.simpleFunctionHeuristic_ = function(node) {
- var children = node.childNodes;
- if (children.length == 0) {
- return true;
- }
- if (children.length > 1) {
- return false;
- }
- var child = children[0];
- if (child.type == cvox.SemanticAttr.Type.INFIXOP) {
- if (child.role != cvox.SemanticAttr.Role.IMPLICIT) {
- return false;
- }
- if (child.childNodes.some(cvox.SemanticTree.attrPred_('type', 'INFIXOP'))) {
- return false;
- }
- }
- return true;
-};
-
-
-/**
- * Predicate implementing the boundary criteria for prefix functions and big
- * operators:
- * 1. an explicit operator,
- * 2. a relation symbol, or
- * 3. some punctuation.
- * @param {cvox.SemanticTree.Node} node A semantic node.
- * @return {boolean} True if the node meets the boundary criteria.
- * @private
- */
-cvox.SemanticTree.prefixFunctionBoundary_ = function(node) {
- return cvox.SemanticTree.attrPred_('type', 'OPERATOR')(node) ||
- cvox.SemanticTree.generalFunctionBoundary_(node);
-};
-
-
-/**
- * Predicate implementing the boundary criteria for integrals dx on two nodes.
- * @param {cvox.SemanticTree.Node} firstNode A semantic node.
- * @param {cvox.SemanticTree.Node} secondNode The direct neighbour of first
- * Node.
- * @return {boolean} True if the second node exists and the first node is a 'd'.
- * @private
- */
-cvox.SemanticTree.integralDxBoundary_ = function(
- firstNode, secondNode) {
- return !!secondNode &&
- cvox.SemanticTree.attrPred_('type', 'IDENTIFIER')(secondNode) &&
- cvox.SemanticAttr.isCharacterD(firstNode.textContent);
-};
-
-
-/**
- * Predicate implementing the boundary criteria for integrals dx on a single
- * node.
- * @param {cvox.SemanticTree.Node} node A semantic node.
- * @return {boolean} True if the node meets the boundary criteria.
- * @private
- */
-cvox.SemanticTree.integralDxBoundarySingle_ = function(node) {
- if (cvox.SemanticTree.attrPred_('type', 'IDENTIFIER')(node)) {
- var firstChar = node.textContent[0];
- return firstChar && node.textContent[1] &&
- cvox.SemanticAttr.isCharacterD(firstChar);
- }
- return false;
-};
-
-
-/**
- * Predicate implementing the general boundary criteria for function operators:
- * 1. a relation symbol,
- * 2. some punctuation.
- * @param {cvox.SemanticTree.Node} node A semantic node.
- * @return {boolean} True if the node meets the boundary criteria.
- * @private
- */
-cvox.SemanticTree.generalFunctionBoundary_ = function(node) {
- return cvox.SemanticTree.attrPred_('type', 'RELATION')(node) ||
- cvox.SemanticTree.attrPred_('type', 'PUNCTUATION')(node);
-};
-
-
-/**
- * Rewrites tables into matrices or case statements in a list of nodes.
- * @param {!Array<cvox.SemanticTree.Node>} nodes List of nodes to rewrite.
- * @return {!Array<cvox.SemanticTree.Node>} The new list of nodes.
- * @private
- */
-cvox.SemanticTree.prototype.processTablesInRow_ = function(nodes) {
- // First we process all matrices:
- var partition = cvox.SemanticTree.partitionNodes_(
- nodes, cvox.SemanticTree.tableIsMatrixOrVector_);
- var result = [];
- for (var i = 0, matrix; matrix = partition.rel[i]; i++) {
- result = result.concat(partition.comp.shift());
- result.push(this.tableToMatrixOrVector_(matrix));
- }
- result = result.concat(partition.comp.shift());
- // Process the remaining tables for cases.
- partition = cvox.SemanticTree.partitionNodes_(
- result, cvox.SemanticTree.isTableOrMultiline_);
- result = [];
- for (var i = 0, table; table = partition.rel[i]; i++) {
- var prevNodes = partition.comp.shift();
- if (cvox.SemanticTree.tableIsCases_(table, prevNodes)) {
- this.tableToCases_(
- table, /** @type {!cvox.SemanticTree.Node} */ (prevNodes.pop()));
- }
- result = result.concat(prevNodes);
- result.push(table);
- }
- return result.concat(partition.comp.shift());
-};
-
-
-/**
- * Decides if a node is a table or multiline element.
- * @param {cvox.SemanticTree.Node} node A node.
- * @return {boolean} True if node is either table or multiline.
- * @private
- */
-cvox.SemanticTree.isTableOrMultiline_ = function(node) {
- return !!node && (cvox.SemanticTree.attrPred_('type', 'TABLE')(node) ||
- cvox.SemanticTree.attrPred_('type', 'MULTILINE')(node));
-};
-
-
-/**
- * Heuristic to decide if we have a matrix: An expression fenced on both sides
- * without any other content is considered a fenced node.
- * @param {cvox.SemanticTree.Node} node A node.
- * @return {boolean} True if we believe we have a matrix.
- * @private
- */
-cvox.SemanticTree.tableIsMatrixOrVector_ = function(node) {
- return !!node && cvox.SemanticTree.attrPred_('type', 'FENCED')(node) &&
- cvox.SemanticTree.attrPred_('role', 'LEFTRIGHT')(node) &&
- node.childNodes.length == 1 &&
- cvox.SemanticTree.isTableOrMultiline_(node.childNodes[0]);
-};
-
-
-/**
- * Replaces a fenced node by a matrix or vector node.
- * @param {!cvox.SemanticTree.Node} node The fenced node to be rewritten.
- * @return {!cvox.SemanticTree.Node} The matrix or vector node.
- * @private
- */
-cvox.SemanticTree.prototype.tableToMatrixOrVector_ = function(node) {
- var matrix = node.childNodes[0];
- var type = cvox.SemanticTree.attrPred_('type', 'MULTILINE')(matrix) ?
- 'VECTOR' : 'MATRIX';
- matrix.type = cvox.SemanticAttr.Type[type];
- node.contentNodes.forEach(goog.bind(matrix.appendContentNode_, matrix));
- for (var i = 0, row; row = matrix.childNodes[i]; i++) {
- cvox.SemanticTree.assignRoleToRow_(row, cvox.SemanticAttr.Role[type]);
- }
- return matrix;
-};
-
-
-/**
- * Heuristic to decide if we have a case statement: An expression with a
- * singular open fence before it.
- * @param {!cvox.SemanticTree.Node} table A table node.
- * @param {!Array<cvox.SemanticTree.Node>} prevNodes A list of previous nodes.
- * @return {boolean} True if we believe we have a case statement.
- * @private
- */
-cvox.SemanticTree.tableIsCases_ = function(table, prevNodes) {
- return prevNodes.length > 0 &&
- cvox.SemanticTree.attrPred_('role', 'OPENFENCE')(
- prevNodes[prevNodes.length - 1]);
-};
-
-
-/**
- * Makes case node out of a table and a fence.
- * @param {!cvox.SemanticTree.Node} table The table containing the cases.
- * @param {!cvox.SemanticTree.Node} openFence The left delimiter of the case
- * statement.
- * @return {!cvox.SemanticTree.Node} The cases node.
- * @private
- */
-cvox.SemanticTree.prototype.tableToCases_ = function(table, openFence) {
- for (var i = 0, row; row = table.childNodes[i]; i++) {
- cvox.SemanticTree.assignRoleToRow_(row, cvox.SemanticAttr.Role.CASES);
- // }
- }
- table.type = cvox.SemanticAttr.Type.CASES;
- table.appendContentNode_(openFence);
- return table;
-};
-
-
-// TODO (sorge) This heuristic is very primitive. We could start reworking
-// multilines, by combining all cells, semantically rewriting the entire line
-// and see if there are any similarities. Alternatively, we could look for
-// similarities in columns (e.g., single relation symbols, like equalities or
-// inequalities in the same column could indicate an equation array).
-/**
- * Heuristic to decide if we have a multiline formula. A table is considered a
- * multiline formula if it does not have any separate cells.
- * @param {!cvox.SemanticTree.Node} table A table node.
- * @return {boolean} True if we believe we have a mulitline formula.
- * @private
- */
-cvox.SemanticTree.tableIsMultiline_ = function(table) {
- return table.childNodes.every(
- function(row) {
- var length = row.childNodes.length;
- return length <= 1;});
-};
-
-
-/**
- * Rewrites a table to multiline structure, simplifying it by getting rid of the
- * cell hierarchy level.
- * @param {!cvox.SemanticTree.Node} table The node to be rewritten a multiline.
- * @private
- */
-cvox.SemanticTree.prototype.tableToMultiline_ = function(table) {
- table.type = cvox.SemanticAttr.Type.MULTILINE;
- for (var i = 0, row; row = table.childNodes[i]; i++) {
- cvox.SemanticTree.rowToLine_(row, cvox.SemanticAttr.Role.MULTILINE);
- }
-};
-
-
-/**
- * Converts a row that only contains one cell into a single line.
- * @param {!cvox.SemanticTree.Node} row The row to convert.
- * @param {cvox.SemanticAttr.Role=} role The new role for the line.
- * @private
- */
-cvox.SemanticTree.rowToLine_ = function(row, role) {
- role = role || cvox.SemanticAttr.Role.UNKNOWN;
- if (cvox.SemanticTree.attrPred_('type', 'ROW')(row) &&
- row.childNodes.length == 1 &&
- cvox.SemanticTree.attrPred_('type', 'CELL')(row.childNodes[0])) {
- row.type = cvox.SemanticAttr.Type.LINE;
- row.role = role;
- row.childNodes = row.childNodes[0].childNodes;
- }
-};
-
-
-/**
- * Assign a row and its contained cells a new role value.
- * @param {!cvox.SemanticTree.Node} row The row to be updated.
- * @param {!cvox.SemanticAttr.Role} role The new role for the row and its cells.
- * @private
- */
-cvox.SemanticTree.assignRoleToRow_ = function(row, role) {
- if (cvox.SemanticTree.attrPred_('type', 'LINE')(row)) {
- row.role = role;
- return;
- }
- if (cvox.SemanticTree.attrPred_('type', 'ROW')(row)) {
- row.role = role;
- var cellPred = cvox.SemanticTree.attrPred_('type', 'CELL');
- row.childNodes.forEach(function(cell) {
- if (cellPred(cell)) {
- cell.role = role;
- }
- });
- }
-};
-
-
-/**
- * Splits a list of nodes wrt. to a given predicate.
- * @param {Array<cvox.SemanticTree.Node>} nodes A list of nodes.
- * @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
- * partitioning relation.
- * @param {boolean=} reverse If true slicing is done from the end.
- * @return {{head: !Array<cvox.SemanticTree.Node>,
- * div: cvox.SemanticTree.Node,
- * tail: !Array<cvox.SemanticTree.Node>}} The split list.
- * @private
- */
-cvox.SemanticTree.sliceNodes_ = function(nodes, pred, reverse) {
- if (reverse) {
- nodes.reverse();
- }
- var head = [];
- for (var i = 0, node; node = nodes[i]; i++) {
- if (pred(node)) {
- if (reverse) {
- return {head: nodes.slice(i + 1).reverse(),
- div: node,
- tail: head.reverse()};
- }
- return {head: head,
- div: node,
- tail: nodes.slice(i + 1)};
- }
- head.push(node);
- }
- if (reverse) {
- return {head: [], div: null, tail: head.reverse()};
- }
- return {head: head, div: null, tail: []};
-};
-
-
-/**
- * Partitions a list of nodes wrt. to a given predicate. Effectively works like
- * a PER on the ordered set of nodes.
- * @param {!Array<!cvox.SemanticTree.Node>} nodes A list of nodes.
- * @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
- * partitioning relation.
- * @return {{rel: !Array<cvox.SemanticTree.Node>,
- * comp: !Array<!Array<cvox.SemanticTree.Node>>}}
- * The partitioning given in terms of a collection of elements satisfying
- * the predicate and a collection of complementary sets lying inbetween the
- * related elements. Observe that we always have |comp| = |rel| + 1.
- *
- * Example: On input [a, r_1, b, c, r_2, d, e, r_3] where P(r_i) holds, we
- * get as output: {rel: [r_1, r_2, r_3], comp: [[a], [b, c], [d, e], []].
- * @private
- */
-cvox.SemanticTree.partitionNodes_ = function(nodes, pred) {
- var restNodes = nodes;
- var rel = [];
- var comp = [];
-
- do {
- var result = cvox.SemanticTree.sliceNodes_(restNodes, pred);
- comp.push(result.head);
- rel.push(result.div);
- restNodes = result.tail;
- } while (result.div);
- rel.pop();
- return {rel: rel, comp: comp};
-};
-
-
-/**
- * Constructs a predicate to check the semantic attribute of a node.
- * @param {!string} prop The property of a node.
- * @param {!string} attr The attribute.
- * @return {function(cvox.SemanticTree.Node): boolean} The predicate.
- * @private
- */
-
-cvox.SemanticTree.attrPred_ = function(prop, attr) {
- var getAttr = function(prop) {
- switch (prop) {
- case 'type': return cvox.SemanticAttr.Type[attr];
- case 'role': return cvox.SemanticAttr.Role[attr];
- case 'font': return cvox.SemanticAttr.Font[attr];
- }
- };
-
- return function(node) {return node[prop] == getAttr(prop);};
-};