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. 4 5 /** 6 * @fileoverview Rule store for math syntax tree nodes. 7 */ 8 9 goog.provide('cvox.MathStore'); 10 11 goog.require('cvox.AbstractTts'); 12 goog.require('cvox.BaseRuleStore'); 13 goog.require('cvox.ChromeVox'); 14 goog.require('cvox.NavMathDescription'); 15 goog.require('cvox.SpeechRule'); 16 goog.require('cvox.TraverseMath'); 17 18 19 /** 20 * A store for Math rules. 21 * @constructor 22 * @extends {cvox.BaseRuleStore} 23 */ 24 cvox.MathStore = function() { 25 goog.base(this); 26 27 /** 28 * @override 29 */ 30 this.dynamicCstrAttribs = [ 31 cvox.SpeechRule.DynamicCstrAttrib.DOMAIN, 32 cvox.SpeechRule.DynamicCstrAttrib.STYLE 33 ]; 34 35 /** 36 * @override 37 */ 38 this.defaultTtsProps = [cvox.AbstractTts.PITCH, cvox.AbstractTts.RATE]; 39 }; 40 goog.inherits(cvox.MathStore, cvox.BaseRuleStore); 41 42 /** This adds domain to dynamic constraint annotation. */ 43 cvox.SpeechRule.DynamicCstrAttrib.DOMAIN = 'domain'; 44 45 46 /** 47 * @override 48 */ 49 cvox.MathStore.prototype.defineRule = function( 50 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 */ 72 cvox.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 */ 88 cvox.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 */ 103 cvox.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 */ 126 cvox.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 */ 143 cvox.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 */ 165 cvox.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 */ 178 cvox.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 */ 191 cvox.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 */ 244 cvox.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 */ 266 cvox.MathStore.removeEmpty_ = function(strs) { 267 return strs.filter(function(str) {return str;}); 268 }; 269