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 Implementation of the speech rule engine.
8 * The speech rule engine chooses and applies speech rules. Rules are chosen
9 * from a set of rule stores wrt. their applicability to a node in a particular
10 * markup type such as MathML or HTML. Rules are dispatched either by
11 * recursively computing new nodes and applicable rules or, if no further rule
12 * is applicable to a current node, by computing a speech object in the form of
13 * an array of navigation descriptions.
15 * Consequently the rule engine is parameterisable wrt. rule stores and
19 goog
.provide('cvox.SpeechRuleEngine');
21 goog
.require('cvox.BaseRuleStore');
22 goog
.require('cvox.NavDescription');
23 goog
.require('cvox.NavMathDescription');
24 goog
.require('cvox.SpeechRule');
30 cvox
.SpeechRuleEngine = function() {
32 * The currently active speech rule store.
33 * @type {cvox.BaseRuleStore}
36 this.activeStore_
= null;
39 * Dynamic constraint annotation.
40 * @type {!cvox.SpeechRule.DynamicCstr}
42 this.dynamicCstr
= {};
43 this.dynamicCstr
[cvox
.SpeechRule
.DynamicCstrAttrib
.STYLE
] = 'short';
45 goog
.addSingletonGetter(cvox
.SpeechRuleEngine
);
49 * Parameterizes the speech rule engine.
50 * @param {cvox.BaseRuleStore} store A speech rule store.
52 cvox
.SpeechRuleEngine
.prototype.parameterize = function(store
) {
56 if (err
.name
== 'StoreError') {
57 console
.log('Store Error:', err
.message
);
63 this.activeStore_
= store
;
68 * Parameterizes the dynamic constraint annotation for the speech rule
69 * engine. This is a separate function as this can be done interactively, while
70 * a particular speech rule store is active.
71 * @param {cvox.SpeechRule.DynamicCstr} dynamic The new dynamic constraint.
73 cvox
.SpeechRuleEngine
.prototype.setDynamicConstraint = function(dynamic
) {
75 this.dynamicCstr
= dynamic
;
81 * Constructs a string from the node and the given expression.
82 * @param {!Node} node The initial node.
83 * @param {string} expr An Xpath expression string, a name of a custom
84 * function or a string.
85 * @return {string} The result of applying expression to node.
87 cvox
.SpeechRuleEngine
.prototype.constructString = function(node
, expr
) {
91 if (expr
.charAt(0) == '"') {
92 return expr
.slice(1, -1);
94 var func
= this.activeStore_
.customStrings
.lookup(expr
);
96 // We always return the result of the custom function, in case it
97 // deliberately computes the empty string!
100 // Finally we assume expr to be an xpath expression and calculate a string
101 // value from the node.
102 return cvox
.XpathUtil
.evaluateString(expr
, node
);
106 // Dispatch functionality.
108 * Computes a speech object for a given node. Returns the empty list if
110 * @param {Node} node The node to be evaluated.
111 * @return {!Array<cvox.NavDescription>} A list of navigation descriptions for
114 cvox
.SpeechRuleEngine
.prototype.evaluateNode = function(node
) {
118 return this.evaluateTree_(node
);
123 * Applies rules recursively to compute the final speech object.
124 * @param {!Node} node Node to apply the speech rule to.
125 * @return {!Array<cvox.NavDescription>} A list of Navigation descriptions.
128 cvox
.SpeechRuleEngine
.prototype.evaluateTree_ = function(node
) {
129 var rule
= this.activeStore_
.lookupRule(node
, this.dynamicCstr
);
131 return this.activeStore_
.evaluateDefault(node
);
133 var components
= rule
.action
.components
;
135 for (var i
= 0, component
; component
= components
[i
]; i
++) {
137 var content
= component
['content'] || '';
138 switch (component
.type
) {
139 case cvox
.SpeechRule
.Type
.NODE
:
140 var selected
= this.activeStore_
.applyQuery(node
, content
);
142 navs
= this.evaluateTree_(selected
);
145 case cvox
.SpeechRule
.Type
.MULTI
:
146 selected
= this.activeStore_
.applySelector(node
, content
);
147 if (selected
.length
> 0) {
148 navs
= this.evaluateNodeList_(
150 component
['sepFunc'],
151 this.constructString(node
, component
['separator']),
152 component
['ctxtFunc'],
153 this.constructString(node
, component
['context']));
156 case cvox
.SpeechRule
.Type
.TEXT
:
157 selected
= this.constructString(node
, content
);
159 navs
= [new cvox
.NavDescription({text
: selected
})];
162 case cvox
.SpeechRule
.Type
.PERSONALITY
:
164 navs
= [new cvox
.NavDescription({text
: content
})];
166 // Adding overall context if it exists.
167 if (navs
[0] && component
['context'] &&
168 component
.type
!= cvox
.SpeechRule
.Type
.MULTI
) {
170 this.constructString(node
, component
['context']) +
171 (navs
[0]['context'] || '');
173 // Adding personality to the nav descriptions.
174 result
= result
.concat(this.addPersonality_(navs
, component
));
181 * Evaluates a list of nodes into a list of navigation descriptions.
182 * @param {!Array<Node>} nodes Array of nodes.
183 * @param {string} sepFunc Name of a function used to compute a separator
184 * between every element.
185 * @param {string} separator A string that is used as argument to the sepFunc or
186 * interspersed directly between each node if sepFunc is not supplied.
187 * @param {string} ctxtFunc Name of a function applied to compute the context
188 * for every element in the list.
189 * @param {string} context Additional context string that is given to the
190 * ctxtFunc function or used directly if ctxtFunc is not supplied.
191 * @return {Array<cvox.NavDescription>} A list of Navigation descriptions.
194 cvox
.SpeechRuleEngine
.prototype.evaluateNodeList_ = function(
195 nodes
, sepFunc
, separator
, ctxtFunc
, context
) {
199 var sep
= separator
|| '';
200 var cont
= context
|| '';
201 var cFunc
= this.activeStore_
.contextFunctions
.lookup(ctxtFunc
);
202 var ctxtClosure
= cFunc
? cFunc(nodes
, cont
) : function() {return cont
;};
203 var sFunc
= this.activeStore_
.contextFunctions
.lookup(sepFunc
);
204 var sepClosure
= sFunc
? sFunc(nodes
, sep
) : function() {return sep
;};
206 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
207 var navs
= this.evaluateTree_(node
);
208 if (navs
.length
> 0) {
209 navs
[0]['context'] = ctxtClosure() + (navs
[0]['context'] || '');
210 result
= result
.concat(navs
);
211 if (i
< nodes
.length
- 1) {
212 var text
= sepClosure();
214 result
.push(new cvox
.NavDescription({text
: text
}));
224 * Maps properties in speech rules to personality properties.
225 * @type {{pitch : string,
231 cvox
.SpeechRuleEngine
.propMap
= {'pitch': cvox
.AbstractTts
.RELATIVE_PITCH
,
232 'rate': cvox
.AbstractTts
.RELATIVE_RATE
,
233 'volume': cvox
.AbstractTts
.RELATIVE_VOLUME
,
234 'pause': cvox
.AbstractTts
.PAUSE
239 * Adds personality to every Navigation Descriptions in input list.
240 * @param {Array<cvox.NavDescription>} navs A list of Navigation descriptions.
241 * @param {Object} props Property dictionary.
242 * TODO (sorge) Fully specify, when we have finalised the speech rule
244 * @return {Array<cvox.NavDescription>} The modified array.
247 cvox
.SpeechRuleEngine
.prototype.addPersonality_ = function(navs
, props
) {
248 var personality
= {};
249 for (var key
in cvox
.SpeechRuleEngine
.propMap
) {
250 var value
= parseFloat(props
[key
]);
252 personality
[cvox
.SpeechRuleEngine
.propMap
[key
]] = value
;
255 navs
.forEach(goog
.bind(function(nav
) {
256 this.addRelativePersonality_(nav
, personality
);
257 this.resetPersonality_(nav
);
264 * Adds relative personality entries to the personality of a Navigation
266 * @param {cvox.NavDescription|cvox.NavMathDescription} nav Nav Description.
267 * @param {!Object} personality Dictionary with relative personality entries.
268 * @return {cvox.NavDescription|cvox.NavMathDescription} Updated description.
271 cvox
.SpeechRuleEngine
.prototype.addRelativePersonality_ = function(
273 if (!nav
['personality']) {
274 nav
['personality'] = personality
;
277 var navPersonality
= nav
['personality'];
278 for (var p
in personality
) {
279 // Although values could exceed boundaries, they will be limited to the
280 // correct interval via the call to
281 // cvox.AbstractTts.prototype.mergeProperties in
282 // cvox.TtsBackground.prototype.speak
283 if (navPersonality
[p
] && typeof(navPersonality
[p
]) == 'number') {
284 navPersonality
[p
] = navPersonality
[p
] + personality
[p
];
286 navPersonality
[p
] = personality
[p
];
294 * Resets personalities to default values if necessary.
295 * @param {cvox.NavDescription|cvox.NavMathDescription} nav Nav Description.
298 cvox
.SpeechRuleEngine
.prototype.resetPersonality_ = function(nav
) {
299 if (this.activeStore_
.defaultTtsProps
) {
300 for (var i
= 0, prop
; prop
= this.activeStore_
.defaultTtsProps
[i
]; i
++) {
301 nav
.personality
[prop
] = cvox
.ChromeVox
.tts
.getDefaultProperty(prop
);
308 * Flag for the debug mode of the speech rule engine.
311 cvox
.SpeechRuleEngine
.debugMode
= false;
316 * @param {...*} output Rest elements of debug output.
318 cvox
.SpeechRuleEngine
.outputDebug = function(output
) {
319 if (cvox
.SpeechRuleEngine
.debugMode
) {
320 var outputList
= Array
.prototype.slice
.call(arguments
, 0);
321 console
.log
.apply(console
,
322 ['Speech Rule Engine Debugger:'].concat(outputList
));
328 * Prints the list of all current rules in ChromeVox to the console.
329 * @return {string} A textual representation of all rules in the speech rule
332 cvox
.SpeechRuleEngine
.prototype.toString = function() {
333 var allRules
= this.activeStore_
.findAllRules(function(x
) {return true;});
334 return allRules
.map(function(rule
) {return rule
.toString();}).
340 * Test the precondition of a speech rule in debugging mode.
341 * @param {cvox.SpeechRule} rule A speech rule.
342 * @param {!Node} node DOM node to test applicability of the rule.
344 cvox
.SpeechRuleEngine
.debugSpeechRule = function(rule
, node
) {
345 var store
= cvox
.SpeechRuleEngine
.getInstance().activeStore_
;
347 var prec
= rule
.precondition
;
348 cvox
.SpeechRuleEngine
.outputDebug(
349 prec
.query
, store
.applyQuery(node
, prec
.query
));
350 prec
.constraints
.forEach(
352 cvox
.SpeechRuleEngine
.outputDebug(
353 cstr
, store
.applyConstraint(node
, cstr
));});
359 * Test the precondition of a speech rule in debugging mode.
360 * @param {string} name Rule to debug.
361 * @param {!Node} node DOM node to test applicability of the rule.
363 cvox
.SpeechRuleEngine
.debugNamedSpeechRule = function(name
, node
) {
364 var store
= cvox
.SpeechRuleEngine
.getInstance().activeStore_
;
365 var allRules
= store
.findAllRules(
366 function(rule
) {return rule
.name
== name
;});
367 for (var i
= 0, rule
; rule
= allRules
[i
]; i
++) {
368 cvox
.SpeechRuleEngine
.outputDebug('Rule', name
, 'number', i
);
369 cvox
.SpeechRuleEngine
.debugSpeechRule(rule
, node
);