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 An interface definition of a speech rule. 7 * 8 * A speech rule is a data structure along with supporting methods that 9 * stipulates how to transform a tree structure such as XML, a browser DOM, or 10 * HTML into a format (usually strings) suitable for rendering by a 11 * text-to-speech engine. 12 * 13 * Speech rules consists of a variable number of speech rule components. Each 14 * component describes how to construct a single utterance. Text-to-speech 15 * renders the components in order. 16 */ 17 18 goog.provide('cvox.SpeechRule'); 19 goog.provide('cvox.SpeechRule.Action'); 20 goog.provide('cvox.SpeechRule.Component'); 21 goog.provide('cvox.SpeechRule.DynamicCstr'); 22 goog.provide('cvox.SpeechRule.Precondition'); 23 goog.provide('cvox.SpeechRule.Type'); 24 25 26 /** 27 * Creates a speech rule with precondition, actions and admin information. 28 * @constructor 29 * @param {string} name The name of the rule. 30 * @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraint annotations 31 * of the rule. 32 * @param {cvox.SpeechRule.Precondition} prec Precondition of the rule. 33 * @param {cvox.SpeechRule.Action} action Action of the speech rule. 34 */ 35 cvox.SpeechRule = function(name, dynamic, prec, action) { 36 /** @type {string} */ 37 this.name = name; 38 /** @type {cvox.SpeechRule.DynamicCstr} */ 39 this.dynamicCstr = dynamic; 40 /** @type {cvox.SpeechRule.Precondition} */ 41 this.precondition = prec; 42 /** @type {cvox.SpeechRule.Action} */ 43 this.action = action; 44 }; 45 46 47 /** 48 * 49 * @override 50 */ 51 cvox.SpeechRule.prototype.toString = function() { 52 var cstrStrings = []; 53 for (var key in this.dynamicCstr) { 54 cstrStrings.push(this.dynamicCstr[key]); 55 } 56 return this.name + ' | ' + cstrStrings.join('.') + ' | ' + 57 this.precondition.toString() + ' ==> ' + 58 this.action.toString(); 59 }; 60 61 62 /** 63 * Mapping for types of speech rule components. 64 * @enum {string} 65 */ 66 cvox.SpeechRule.Type = { 67 NODE: 'NODE', 68 MULTI: 'MULTI', 69 TEXT: 'TEXT', 70 PERSONALITY: 'PERSONALITY' 71 }; 72 73 74 /** 75 * Maps a string to a valid speech rule type. 76 * @param {string} str Input string. 77 * @return {cvox.SpeechRule.Type} 78 */ 79 cvox.SpeechRule.Type.fromString = function(str) { 80 switch (str) { 81 case '[n]': return cvox.SpeechRule.Type.NODE; 82 case '[m]': return cvox.SpeechRule.Type.MULTI; 83 case '[t]': return cvox.SpeechRule.Type.TEXT; 84 case '[p]': return cvox.SpeechRule.Type.PERSONALITY; 85 default: throw 'Parse error: ' + str; 86 } 87 }; 88 89 90 /** 91 * Maps a speech rule type to a human-readable string. 92 * @param {cvox.SpeechRule.Type} speechType 93 * @return {string} Output string. 94 */ 95 cvox.SpeechRule.Type.toString = function(speechType) { 96 switch (speechType) { 97 case cvox.SpeechRule.Type.NODE: return '[n]'; 98 case cvox.SpeechRule.Type.MULTI: return '[m]'; 99 case cvox.SpeechRule.Type.TEXT: return '[t]'; 100 case cvox.SpeechRule.Type.PERSONALITY: return '[p]'; 101 default: throw 'Unknown type error: ' + speechType; 102 } 103 }; 104 105 106 /** 107 * Defines a component within a speech rule. 108 * @param {{type: cvox.SpeechRule.Type, content: string}} kwargs The input 109 * component in JSON format. 110 * @constructor 111 */ 112 cvox.SpeechRule.Component = function(kwargs) { 113 /** @type {cvox.SpeechRule.Type} */ 114 this.type = kwargs.type; 115 116 /** @type {string} */ 117 this.content = kwargs.content; 118 }; 119 120 121 /** 122 * Parses a valid string representation of a speech component into a Component 123 * object. 124 * @param {string} input The input string. 125 * @return {cvox.SpeechRule.Component} The resulting component. 126 */ 127 cvox.SpeechRule.Component.fromString = function(input) { 128 // The output JSON. 129 var output = {}; 130 131 // Parse the type. 132 output.type = cvox.SpeechRule.Type.fromString(input.substring(0, 3)); 133 134 // Prep the rest of the parsing. 135 var rest = input.slice(3).trimLeft(); 136 if (!rest) { 137 throw new cvox.SpeechRule.OutputError('Missing content.'); 138 } 139 140 switch (output.type) { 141 case cvox.SpeechRule.Type.TEXT: 142 if (rest[0] == '"') { 143 var quotedString = cvox.SpeechRule.splitString_(rest, '\\(')[0].trim(); 144 if (quotedString.slice(-1) != '"') { 145 throw new cvox.SpeechRule.OutputError('Invalid string syntax.'); 146 } 147 output.content = quotedString; 148 rest = rest.slice(quotedString.length).trim(); 149 if (rest.indexOf('(') == -1) { 150 rest = ''; 151 } 152 // This break is conditional. If the content is not an explicit string, 153 // it can be treated like node and multi type. 154 break; 155 } 156 case cvox.SpeechRule.Type.NODE: 157 case cvox.SpeechRule.Type.MULTI: 158 var bracket = rest.indexOf(' ('); 159 if (bracket == -1) { 160 output.content = rest.trim(); 161 rest = ''; 162 break; 163 } 164 output.content = rest.substring(0, bracket).trim(); 165 rest = rest.slice(bracket).trimLeft(); 166 break; 167 } 168 output = new cvox.SpeechRule.Component(output); 169 if (rest) { 170 output.addAttributes(rest); 171 } 172 return output; 173 }; 174 175 176 /** 177 * @override 178 */ 179 cvox.SpeechRule.Component.prototype.toString = function() { 180 var strs = ''; 181 strs += cvox.SpeechRule.Type.toString(this.type); 182 strs += this.content ? ' ' + this.content : ''; 183 var attribs = this.getAttributes(); 184 if (attribs.length > 0) { 185 strs += ' (' + attribs.join(', ') + ')'; 186 } 187 return strs; 188 }; 189 190 191 /** 192 * Adds a single attribute to the component. 193 * @param {string} attr String representation of an attribute. 194 */ 195 cvox.SpeechRule.Component.prototype.addAttribute = function(attr) { 196 var colon = attr.indexOf(':'); 197 if (colon == -1) { 198 this[attr.trim()] = 'true'; 199 } else { 200 this[attr.substring(0, colon).trim()] = attr.slice(colon + 1).trim(); 201 } 202 }; 203 204 205 /** 206 * Adds a list of attributes to the component. 207 * @param {string} attrs String representation of attribute list. 208 */ 209 cvox.SpeechRule.Component.prototype.addAttributes = function(attrs) { 210 if (attrs[0] != '(' || attrs.slice(-1) != ')') { 211 throw new cvox.SpeechRule.OutputError( 212 'Invalid attribute expression: ' + attrs); 213 } 214 var attribs = cvox.SpeechRule.splitString_(attrs.slice(1, -1), ','); 215 for (var i = 0; i < attribs.length; i++) { 216 this.addAttribute(attribs[i]); 217 } 218 }; 219 220 221 /** 222 * Transforms the attributes of an object into a list of strings. 223 * @return {Array.<string>} List of translated attribute:value strings. 224 */ 225 cvox.SpeechRule.Component.prototype.getAttributes = function() { 226 var attribs = []; 227 for (var key in this) { 228 if (key != 'content' && key != 'type' && typeof(this[key]) != 'function') { 229 attribs.push(key + ':' + this[key]); 230 } 231 } 232 return attribs; 233 }; 234 235 236 /** 237 * A speech rule is a collection of speech components. 238 * @param {Array.<cvox.SpeechRule.Component>} components The input rule. 239 * @constructor 240 */ 241 cvox.SpeechRule.Action = function(components) { 242 /** @type {Array.<cvox.SpeechRule.Component>} */ 243 this.components = components; 244 }; 245 246 247 /** 248 * Parses an input string into a speech rule class object. 249 * @param {string} input The input string. 250 * @return {cvox.SpeechRule.Action} The resulting object. 251 */ 252 cvox.SpeechRule.Action.fromString = function(input) { 253 var comps = cvox.SpeechRule.splitString_(input, ';') 254 .filter(function(x) {return x.match(/\S/);}) 255 .map(function(x) {return x.trim();}); 256 var newComps = []; 257 for (var i = 0; i < comps.length; i++) { 258 var comp = cvox.SpeechRule.Component.fromString(comps[i]); 259 if (comp) { 260 newComps.push(comp); 261 } 262 } 263 return new cvox.SpeechRule.Action(newComps); 264 }; 265 266 267 /** 268 * @override 269 */ 270 cvox.SpeechRule.Action.prototype.toString = function() { 271 var comps = this.components.map(function(c) { return c.toString(); }); 272 return comps.join('; '); 273 }; 274 275 276 // TODO (sorge) Separatation of xpath expressions and custom functions. 277 // Also test validity of xpath expressions. 278 /** 279 * Constructs a valid precondition for a speech rule. 280 * @param {string} query A node selector function or xpath expression. 281 * @param {Array.<string>=} opt_constraints A list of constraint functions. 282 * @constructor 283 */ 284 cvox.SpeechRule.Precondition = function(query, opt_constraints) { 285 /** @type {string} */ 286 this.query = query; 287 288 /** @type {!Array.<string>} */ 289 this.constraints = opt_constraints || []; 290 }; 291 292 293 /** 294 * @override 295 */ 296 cvox.SpeechRule.Precondition.prototype.toString = function() { 297 var constrs = this.constraints.join(', '); 298 return this.query + ', ' + constrs; 299 }; 300 301 302 /** 303 * Split a string wrt. a given separator symbol while not splitting inside of a 304 * double quoted string. For example, splitting 305 * '[t] "matrix; 3 by 3"; [n] ./*[1]' with separators ';' would yield 306 * ['[t] "matrix; 3 by 3"', ' [n] ./*[1]']. 307 * @param {string} str String to be split. 308 * @param {string} sep Separator symbol. 309 * @return {Array.<string>} A list of single component strings. 310 * @private 311 */ 312 cvox.SpeechRule.splitString_ = function(str, sep) { 313 var strList = []; 314 var prefix = ''; 315 316 while (str != '') { 317 var sepPos = str.search(sep); 318 if (sepPos == -1) { 319 if ((str.match(/"/g) || []).length % 2 != 0) { 320 throw new cvox.SpeechRule.OutputError( 321 'Invalid string in expression: ' + str); 322 } 323 strList.push(prefix + str); 324 prefix = ''; 325 str = ''; 326 } else if ( 327 (str.substring(0, sepPos).match(/"/g) || []).length % 2 == 0) { 328 strList.push(prefix + str.substring(0, sepPos)); 329 prefix = ''; 330 str = str.substring(sepPos + 1); 331 } else { 332 var nextQuot = str.substring(sepPos).search('"'); 333 if (nextQuot == -1) { 334 throw new cvox.SpeechRule.OutputError( 335 'Invalid string in expression: ' + str); 336 } else { 337 prefix = prefix + str.substring(0, sepPos + nextQuot + 1); 338 str = str.substring(sepPos + nextQuot + 1); 339 } 340 } 341 } 342 if (prefix) { 343 strList.push(prefix); 344 } 345 return strList; 346 }; 347 348 349 /** 350 * Attributes for dynamic constraints. 351 * We define one default attribute as style. Speech rule stores can add other 352 * attributes later. 353 * @enum {string} 354 */ 355 cvox.SpeechRule.DynamicCstrAttrib = 356 { 357 STYLE: 'style' 358 }; 359 360 361 /** 362 * Dynamic constraints are a means to specialize rules that can be changed 363 * dynamically by the user, for example by choosing different styles, etc. 364 * @typedef {!Object.<cvox.SpeechRule.DynamicCstrAttrib, string>} 365 */ 366 cvox.SpeechRule.DynamicCstr; 367 368 369 /** 370 * Error object for signaling parsing errors. 371 * @param {string} msg The error message. 372 * @constructor 373 * @extends {Error} 374 */ 375 cvox.SpeechRule.OutputError = function(msg) { 376 this.name = 'RuleError'; 377 this.message = msg || ''; 378 }; 379 goog.inherits(cvox.SpeechRule.OutputError, Error); 380