blob: ceda6f57bfb29372ae3183a0ea609248e785f2c5 [file] [log] [blame]
// 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 Rule store for math syntax tree nodes.
*/
goog.provide('cvox.MathStore');
goog.require('cvox.AbstractTts');
goog.require('cvox.BaseRuleStore');
goog.require('cvox.ChromeVox');
goog.require('cvox.NavMathDescription');
goog.require('cvox.SpeechRule');
goog.require('cvox.TraverseMath');
/**
* A store for Math rules.
* @constructor
* @extends {cvox.BaseRuleStore}
*/
cvox.MathStore = function() {
goog.base(this);
/**
* @override
*/
this.dynamicCstrAttribs = [
cvox.SpeechRule.DynamicCstrAttrib.DOMAIN,
cvox.SpeechRule.DynamicCstrAttrib.STYLE
];
/**
* @override
*/
this.defaultTtsProps = [cvox.AbstractTts.PITCH, cvox.AbstractTts.RATE];
};
goog.inherits(cvox.MathStore, cvox.BaseRuleStore);
/** This adds domain to dynamic constraint annotation. */
cvox.SpeechRule.DynamicCstrAttrib.DOMAIN = 'domain';
/**
* @override
*/
cvox.MathStore.prototype.defineRule = function(
name, dynamic, action, query, cstr) {
var dynamicCstr = this.parseDynamicConstraint(dynamic);
var cstrList = Array.prototype.slice.call(arguments, 4);
// We can not use goog.base due to variable number of constraint arguments.
var rule = cvox.MathStore.superClass_.defineRule.apply(
this, [name, dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE],
action, query].concat(cstrList));
// In the superclass the dynamic constraint only contains style annotations.
// We now set the proper dynamic constraint that contains in addition a
// a domain attribute/value pair.
rule.dynamicCstr = dynamicCstr;
this.removeDuplicates(rule);
return rule;
};
/**
* Parses the dynamic constraint for math rules, consisting of a domain and
* style information, given as 'domain.style'.
* @param {string} cstr A string representation of the dynamic constraint.
* @return {!cvox.SpeechRule.DynamicCstr} The dynamic constraint.
*/
cvox.MathStore.prototype.parseDynamicConstraint = function(cstr) {
var domainStyle = cstr.split('.');
if (!domainStyle[0] || !domainStyle[1]) {
throw new cvox.SpeechRule.OutputError('Invalid domain assignment:' + cstr);
}
return cvox.MathStore.createDynamicConstraint(domainStyle[0], domainStyle[1]);
};
/**
* Creates a dynamic constraint annotation for math rules from domain and style
* values.
* @param {string} domain Domain annotation.
* @param {string} style Style annotation.
* @return {!cvox.SpeechRule.DynamicCstr}
*/
cvox.MathStore.createDynamicConstraint = function(domain, style) {
var dynamicCstr = {};
dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.DOMAIN] = domain;
dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE] = style;
return dynamicCstr;
};
/**
* Adds an alias for an existing rule.
* @param {string} name The name of the rule.
* @param {string} dynamic A math domain and style assignment.
* @param {string} query Precondition query of the rule.
* @param {...string} cstr Additional static precondition constraints.
*/
cvox.MathStore.prototype.defineUniqueRuleAlias = function(
name, dynamic, query, cstr) {
var dynamicCstr = this.parseDynamicConstraint(dynamic);
var rule = this.findRule(
goog.bind(
function(rule) {
return rule.name == name &&
this.testDynamicConstraints(dynamicCstr, rule);},
this));
if (!rule) {
throw new cvox.SpeechRule.OutputError(
'Rule named ' + name + ' with style ' + dynamic + ' does not exist.');
}
this.addAlias_(rule, query, Array.prototype.slice.call(arguments, 3));
};
/**
* Adds an alias for an existing rule.
* @param {string} name The name of the rule.
* @param {string} query Precondition query of the rule.
* @param {...string} cstr Additional static precondition constraints.
*/
cvox.MathStore.prototype.defineRuleAlias = function(name, query, cstr) {
var rule = this.findRule(function(rule) {
return rule.name == name;});
if (!rule) {
throw new cvox.SpeechRule.OutputError(
'Rule with named ' + name + ' does not exist.');
}
this.addAlias_(rule, query, Array.prototype.slice.call(arguments, 2));
};
/**
* Adds an alias for an existing rule.
* @param {string} name The name of the rule.
* @param {string} query Precondition query of the rule.
* @param {...string} cstr Additional static precondition constraints.
*/
cvox.MathStore.prototype.defineRulesAlias = function(name, query, cstr) {
var rules = this.findAllRules(function(rule) {return rule.name == name;});
if (rules.length == 0) {
throw new cvox.SpeechRule.OutputError(
'Rule with name ' + name + ' does not exist.');
}
var cstrList = Array.prototype.slice.call(arguments, 2);
rules.forEach(goog.bind(
function(rule) {
this.addAlias_(rule, query, cstrList);
},
this));
};
/**
* Adds a new speech rule as alias of the given rule.
* @param {cvox.SpeechRule} rule The existing rule.
* @param {string} query Precondition query of the rule.
* @param {Array.<string>} cstrList List of additional constraints.
* @private
*/
cvox.MathStore.prototype.addAlias_ = function(rule, query, cstrList) {
var prec = new cvox.SpeechRule.Precondition(query, cstrList);
var newRule = new cvox.SpeechRule(
rule.name, rule.dynamicCstr, prec, rule.action);
newRule.name = rule.name;
this.addRule(newRule);
};
// Evaluator
/**
* @override
*/
cvox.MathStore.prototype.evaluateDefault = function(node) {
return this.evaluateString_(node.textContent);
};
/**
* Evaluates a single string of a math expressions. The method splits the given
* string into components such as single characters, function names or words,
* numbers, etc. and creates the appropriate navigation descriptions.
* @param {string} str A string.
* @return {!Array.<cvox.NavDescription>} Messages for the math expression.
* @private
*/
cvox.MathStore.prototype.evaluateString_ = function(str) {
var descs = new Array();
if (str.match(/^\s+$/)) {
// Nothing but whitespace: Ignore.
return descs;
}
var split = cvox.MathStore.removeEmpty_(str.replace(/\s/g, ' ').split(' '));
for (var i = 0, s; s = split[i]; i++) {
if (s.length == 1) {
descs.push(this.evaluate_(s));
} else if (s.match(/^[a-zA-Z]+$/)) {
descs.push(this.evaluate_(s));
} else {
// Break up string even further wrt. symbols vs alphanum substrings.
var rest = s;
var count = 0;
while (rest) {
var num = rest.match(/^\d+/);
var alpha = rest.match(/^[a-zA-Z]+/);
if (num) {
descs.push(this.evaluate_(num[0]));
rest = rest.substring(num[0].length);
} else if (alpha) {
descs.push(this.evaluate_(alpha[0]));
rest = rest.substring(alpha[0].length);
} else {
// Dealing with surrogate pairs.
var chr = rest[0];
var code = chr.charCodeAt(0);
if (0xD800 <= code && code <= 0xDBFF &&
rest.length > 1 && !isNaN(rest.charCodeAt(1))) {
descs.push(this.evaluate_(rest.slice(0, 2)));
rest = rest.substring(2);
} else {
descs.push(this.evaluate_(rest[0]));
rest = rest.substring(1);
}
}
}
}
}
return descs;
};
/**
* Creates a new Navigation Description for a math expression that be used by
* the background tts.
* @param {string} text to be translated.
* @return {cvox.NavDescription} Navigation description for the
* math expression.
* @private
*/
cvox.MathStore.prototype.evaluate_ = function(text) {
if (cvox.ChromeVox.host['mathMap']) {
// VS: Change this for android!
return cvox.ChromeVox.host['mathMap'].evaluate(
text,
cvox.TraverseMath.getInstance().domain,
cvox.TraverseMath.getInstance().style);
}
return new cvox.NavMathDescription(
{'text': text,
'domain': cvox.TraverseMath.getInstance().domain,
'style': cvox.TraverseMath.getInstance().style
});
};
/**
* Removes all empty strings from an array of strings.
* @param {Array.<string>} strs An array of strings.
* @return {Array.<string>} The cleaned array.
* @private
*/
cvox.MathStore.removeEmpty_ = function(strs) {
return strs.filter(function(str) {return str;});
};