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
);