math_store.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru// Copyright 2014 The Chromium Authors. All rights reserved.
280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru// Use of this source code is governed by a BSD-style license that can be
380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru// found in the LICENSE file.
480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru/**
680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * @fileoverview Rule store for math syntax tree nodes.
780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru */
880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.provide('cvox.MathStore');
1080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.AbstractTts');
1280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.BaseRuleStore');
1380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.ChromeVox');
1480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.NavMathDescription');
1580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.SpeechRule');
1680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.require('cvox.TraverseMath');
1780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru/**
20363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger * A store for Math rules.
2180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * @constructor
2280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * @extends {cvox.BaseRuleStore}
2380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru */
2480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querucvox.MathStore = function() {
2580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru  goog.base(this);
2680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
27363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger  /**
2880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru   * @override
29363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger   */
3080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru  this.dynamicCstrAttribs = [
31363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger    cvox.SpeechRule.DynamicCstrAttrib.DOMAIN,
3280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    cvox.SpeechRule.DynamicCstrAttrib.STYLE
33363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger    ];
3480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
35363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger  /**
3680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru   * @override
3780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru   */
3880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru  this.defaultTtsProps = [cvox.AbstractTts.PITCH, cvox.AbstractTts.RATE];
3980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru};
4080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querugoog.inherits(cvox.MathStore, cvox.BaseRuleStore);
41363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger
42d686ac77c2c485c4a3302eda9c1de597a6f8c568Derek Sollenberger/** This adds domain to dynamic constraint annotation. */
4380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querucvox.SpeechRule.DynamicCstrAttrib.DOMAIN = 'domain';
44363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger
45d686ac77c2c485c4a3302eda9c1de597a6f8c568Derek Sollenberger
4680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru/**
4780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * @override
4880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru */
4980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querucvox.MathStore.prototype.defineRule = function(
5080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    name, dynamic, action, query, cstr) {
51  var dynamicCstr = this.parseDynamicConstraint(dynamic);
52  var cstrList = Array.prototype.slice.call(arguments, 4);
53  // We can not use goog.base due to variable number of constraint arguments.
54  var rule = cvox.MathStore.superClass_.defineRule.apply(
55      this, [name, dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE],
56             action, query].concat(cstrList));
57  // In the superclass the dynamic constraint only contains style annotations.
58  // We now set the proper dynamic constraint that contains in addition a
59  // a domain attribute/value pair.
60  rule.dynamicCstr = dynamicCstr;
61  this.removeDuplicates(rule);
62  return rule;
63};
64
65
66/**
67 * Parses the dynamic constraint for math rules, consisting of a domain and
68 * style information, given as 'domain.style'.
69 * @param {string} cstr A string representation of the dynamic constraint.
70 * @return {!cvox.SpeechRule.DynamicCstr} The dynamic constraint.
71 */
72cvox.MathStore.prototype.parseDynamicConstraint = function(cstr) {
73  var domainStyle = cstr.split('.');
74  if (!domainStyle[0] || !domainStyle[1]) {
75    throw new cvox.SpeechRule.OutputError('Invalid domain assignment:' + cstr);
76  }
77  return cvox.MathStore.createDynamicConstraint(domainStyle[0], domainStyle[1]);
78};
79
80
81/**
82 * Creates a dynamic constraint annotation for math rules from domain and style
83 * values.
84 * @param {string} domain Domain annotation.
85 * @param {string} style Style annotation.
86 * @return {!cvox.SpeechRule.DynamicCstr}
87 */
88cvox.MathStore.createDynamicConstraint = function(domain, style) {
89  var dynamicCstr = {};
90  dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.DOMAIN] = domain;
91  dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE] = style;
92  return dynamicCstr;
93};
94
95
96/**
97 * Adds an alias for an existing rule.
98 * @param {string} name The name of the rule.
99 * @param {string} dynamic A math domain and style assignment.
100 * @param {string} query Precondition query of the rule.
101 * @param {...string} cstr Additional static precondition constraints.
102 */
103cvox.MathStore.prototype.defineUniqueRuleAlias = function(
104    name, dynamic, query, cstr) {
105  var dynamicCstr = this.parseDynamicConstraint(dynamic);
106  var rule = this.findRule(
107    goog.bind(
108      function(rule) {
109        return rule.name == name &&
110          this.testDynamicConstraints(dynamicCstr, rule);},
111      this));
112  if (!rule) {
113    throw new cvox.SpeechRule.OutputError(
114        'Rule named ' + name + ' with style ' + dynamic + ' does not exist.');
115  }
116  this.addAlias_(rule, query, Array.prototype.slice.call(arguments, 3));
117};
118
119
120/**
121 * Adds an alias for an existing rule.
122 * @param {string} name The name of the rule.
123 * @param {string} query Precondition query of the rule.
124 * @param {...string} cstr Additional static precondition constraints.
125 */
126cvox.MathStore.prototype.defineRuleAlias = function(name, query, cstr) {
127  var rule = this.findRule(function(rule) {
128    return rule.name == name;});
129  if (!rule) {
130    throw new cvox.SpeechRule.OutputError(
131      'Rule with named ' + name + ' does not exist.');
132    }
133  this.addAlias_(rule, query, Array.prototype.slice.call(arguments, 2));
134};
135
136
137/**
138 * Adds an alias for an existing rule.
139 * @param {string} name The name of the rule.
140 * @param {string} query Precondition query of the rule.
141 * @param {...string} cstr Additional static precondition constraints.
142 */
143cvox.MathStore.prototype.defineRulesAlias = function(name, query, cstr) {
144  var rules = this.findAllRules(function(rule) {return rule.name == name;});
145  if (rules.length == 0) {
146    throw new cvox.SpeechRule.OutputError(
147        'Rule with name ' + name + ' does not exist.');
148  }
149  var cstrList = Array.prototype.slice.call(arguments, 2);
150  rules.forEach(goog.bind(
151                  function(rule) {
152                    this.addAlias_(rule, query, cstrList);
153                  },
154                  this));
155};
156
157
158/**
159 * Adds a new speech rule as alias of the given rule.
160 * @param {cvox.SpeechRule} rule The existing rule.
161 * @param {string} query Precondition query of the rule.
162 * @param {Array.<string>} cstrList List of additional constraints.
163 * @private
164 */
165cvox.MathStore.prototype.addAlias_ = function(rule, query, cstrList) {
166  var prec = new cvox.SpeechRule.Precondition(query, cstrList);
167  var newRule = new cvox.SpeechRule(
168      rule.name, rule.dynamicCstr, prec, rule.action);
169  newRule.name = rule.name;
170  this.addRule(newRule);
171};
172
173
174// Evaluator
175/**
176 * @override
177 */
178cvox.MathStore.prototype.evaluateDefault = function(node) {
179  return this.evaluateString_(node.textContent);
180};
181
182
183/**
184 * Evaluates a single string of a math expressions. The method splits the given
185 * string into components such as single characters, function names or words,
186 * numbers, etc. and creates the appropriate navigation descriptions.
187 * @param {string} str A string.
188 * @return {!Array.<cvox.NavDescription>} Messages for the math expression.
189 * @private
190 */
191cvox.MathStore.prototype.evaluateString_ = function(str) {
192  var descs = new Array();
193  if (str.match(/^\s+$/)) {
194    // Nothing but whitespace: Ignore.
195    return descs;
196  }
197  var split = cvox.MathStore.removeEmpty_(str.replace(/\s/g, ' ').split(' '));
198  for (var i = 0, s; s = split[i]; i++) {
199    if (s.length == 1) {
200      descs.push(this.evaluate_(s));
201    } else if (s.match(/^[a-zA-Z]+$/)) {
202      descs.push(this.evaluate_(s));
203    } else {
204      // Break up string even further wrt. symbols vs alphanum substrings.
205      var rest = s;
206      var count = 0;
207      while (rest) {
208        var num = rest.match(/^\d+/);
209        var alpha = rest.match(/^[a-zA-Z]+/);
210        if (num) {
211          descs.push(this.evaluate_(num[0]));
212          rest = rest.substring(num[0].length);
213        } else if (alpha) {
214          descs.push(this.evaluate_(alpha[0]));
215          rest = rest.substring(alpha[0].length);
216        } else {
217          // Dealing with surrogate pairs.
218          var chr = rest[0];
219          var code = chr.charCodeAt(0);
220          if (0xD800 <= code && code <= 0xDBFF &&
221              rest.length > 1 && !isNaN(rest.charCodeAt(1))) {
222            descs.push(this.evaluate_(rest.slice(0, 2)));
223            rest = rest.substring(2);
224          } else {
225            descs.push(this.evaluate_(rest[0]));
226            rest = rest.substring(1);
227            }
228        }
229      }
230    }
231  }
232  return descs;
233};
234
235
236/**
237 * Creates a new Navigation Description for a math expression that be used by
238 * the background tts.
239 * @param {string} text to be translated.
240 * @return {cvox.NavDescription} Navigation description for the
241 *     math expression.
242 * @private
243 */
244cvox.MathStore.prototype.evaluate_ = function(text) {
245  if (cvox.ChromeVox.host['mathMap']) {
246    // VS: Change this for android!
247    return cvox.ChromeVox.host['mathMap'].evaluate(
248        text,
249        cvox.TraverseMath.getInstance().domain,
250        cvox.TraverseMath.getInstance().style);
251  }
252  return new cvox.NavMathDescription(
253      {'text': text,
254       'domain': cvox.TraverseMath.getInstance().domain,
255       'style': cvox.TraverseMath.getInstance().style
256      });
257};
258
259
260/**
261 * Removes all empty strings from an array of strings.
262 * @param {Array.<string>} strs An array of strings.
263 * @return {Array.<string>} The cleaned array.
264 * @private
265 */
266cvox.MathStore.removeEmpty_ = function(strs) {
267  return strs.filter(function(str) {return str;});
268};
269