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.
6 * @fileoverview An interface definition of a speech rule.
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.
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.
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');
27 * Creates a speech rule with precondition, actions and admin information.
29 * @param {string} name The name of the rule.
30 * @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraint annotations
32 * @param {cvox.SpeechRule.Precondition} prec Precondition of the rule.
33 * @param {cvox.SpeechRule.Action} action Action of the speech rule.
35 cvox.SpeechRule = function(name, dynamic, prec, action) {
38 /** @type {cvox.SpeechRule.DynamicCstr} */
39 this.dynamicCstr = dynamic;
40 /** @type {cvox.SpeechRule.Precondition} */
41 this.precondition = prec;
42 /** @type {cvox.SpeechRule.Action} */
51 cvox.SpeechRule.prototype.toString = function() {
53 for (var key in this.dynamicCstr) {
54 cstrStrings.push(this.dynamicCstr[key]);
56 return this.name + ' | ' + cstrStrings.join('.') + ' | ' +
57 this.precondition.toString() + ' ==> ' +
58 this.action.toString();
63 * Mapping for types of speech rule components.
66 cvox.SpeechRule.Type = {
70 PERSONALITY: 'PERSONALITY'
75 * Maps a string to a valid speech rule type.
76 * @param {string} str Input string.
77 * @return {cvox.SpeechRule.Type}
79 cvox.SpeechRule.Type.fromString = function(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;
91 * Maps a speech rule type to a human-readable string.
92 * @param {cvox.SpeechRule.Type} speechType
93 * @return {string} Output string.
95 cvox.SpeechRule.Type.toString = function(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;
107 * Defines a component within a speech rule.
108 * @param {{type: cvox.SpeechRule.Type, content: string}} kwargs The input
109 * component in JSON format.
112 cvox.SpeechRule.Component = function(kwargs) {
113 /** @type {cvox.SpeechRule.Type} */
114 this.type = kwargs.type;
116 /** @type {string} */
117 this.content = kwargs.content;
122 * Parses a valid string representation of a speech component into a Component
124 * @param {string} input The input string.
125 * @return {cvox.SpeechRule.Component} The resulting component.
127 cvox.SpeechRule.Component.fromString = function(input) {
132 output.type = cvox.SpeechRule.Type.fromString(input.substring(0, 3));
134 // Prep the rest of the parsing.
135 var rest = input.slice(3).trimLeft();
137 throw new cvox.SpeechRule.OutputError('Missing content.');
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.');
147 output.content = quotedString;
148 rest = rest.slice(quotedString.length).trim();
149 if (rest.indexOf('(') == -1) {
152 // This break is conditional. If the content is not an explicit string,
153 // it can be treated like node and multi type.
156 case cvox.SpeechRule.Type.NODE:
157 case cvox.SpeechRule.Type.MULTI:
158 var bracket = rest.indexOf(' (');
160 output.content = rest.trim();
164 output.content = rest.substring(0, bracket).trim();
165 rest = rest.slice(bracket).trimLeft();
168 output = new cvox.SpeechRule.Component(output);
170 output.addAttributes(rest);
179 cvox.SpeechRule.Component.prototype.toString = function() {
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(', ') + ')';
192 * Adds a single attribute to the component.
193 * @param {string} attr String representation of an attribute.
195 cvox.SpeechRule.Component.prototype.addAttribute = function(attr) {
196 var colon = attr.indexOf(':');
198 this[attr.trim()] = 'true';
200 this[attr.substring(0, colon).trim()] = attr.slice(colon + 1).trim();
206 * Adds a list of attributes to the component.
207 * @param {string} attrs String representation of attribute list.
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);
214 var attribs = cvox.SpeechRule.splitString_(attrs.slice(1, -1), ',');
215 for (var i = 0; i < attribs.length; i++) {
216 this.addAttribute(attribs[i]);
222 * Transforms the attributes of an object into a list of strings.
223 * @return {Array<string>} List of translated attribute:value strings.
225 cvox.SpeechRule.Component.prototype.getAttributes = function() {
227 for (var key in this) {
228 if (key != 'content' && key != 'type' && typeof(this[key]) != 'function') {
229 attribs.push(key + ':' + this[key]);
237 * A speech rule is a collection of speech components.
238 * @param {Array<cvox.SpeechRule.Component>} components The input rule.
241 cvox.SpeechRule.Action = function(components) {
242 /** @type {Array<cvox.SpeechRule.Component>} */
243 this.components = components;
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.
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();});
257 for (var i = 0; i < comps.length; i++) {
258 var comp = cvox.SpeechRule.Component.fromString(comps[i]);
263 return new cvox.SpeechRule.Action(newComps);
270 cvox.SpeechRule.Action.prototype.toString = function() {
271 var comps = this.components.map(function(c) { return c.toString(); });
272 return comps.join('; ');
276 // TODO (sorge) Separatation of xpath expressions and custom functions.
277 // Also test validity of xpath expressions.
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.
284 cvox.SpeechRule.Precondition = function(query, opt_constraints) {
285 /** @type {string} */
288 /** @type {!Array<string>} */
289 this.constraints = opt_constraints || [];
296 cvox.SpeechRule.Precondition.prototype.toString = function() {
297 var constrs = this.constraints.join(', ');
298 return this.query + ', ' + constrs;
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.
312 cvox.SpeechRule.splitString_ = function(str, sep) {
317 var sepPos = str.search(sep);
319 if ((str.match(/"/g) || []).length % 2 != 0) {
320 throw new cvox.SpeechRule.OutputError(
321 'Invalid string in expression: ' + str);
323 strList.push(prefix + str);
327 (str.substring(0, sepPos).match(/"/g) || []).length % 2 == 0) {
328 strList.push(prefix + str.substring(0, sepPos));
330 str = str.substring(sepPos + 1);
332 var nextQuot = str.substring(sepPos).search('"');
333 if (nextQuot == -1) {
334 throw new cvox.SpeechRule.OutputError(
335 'Invalid string in expression: ' + str);
337 prefix = prefix + str.substring(0, sepPos + nextQuot + 1);
338 str = str.substring(sepPos + nextQuot + 1);
343 strList.push(prefix);
350 * Attributes for dynamic constraints.
351 * We define one default attribute as style. Speech rule stores can add other
355 cvox.SpeechRule.DynamicCstrAttrib =
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>}
366 cvox.SpeechRule.DynamicCstr;
370 * Error object for signaling parsing errors.
371 * @param {string} msg The error message.
375 cvox.SpeechRule.OutputError = function(msg) {
376 this.name = 'RuleError';
377 this.message = msg || '';
379 goog.inherits(cvox.SpeechRule.OutputError, Error);