Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / speech_rules / speech_rule_engine.js
blobd7be38210ecbcf710eb5e8b55c206c681ed63717
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.
5 /**
6  * @fileoverview Implementation of the speech rule engine.
7  *
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.
14  *
15  * Consequently the rule engine is parameterisable wrt. rule stores and
16  * evaluator function.
17  */
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');
27 /**
28  * @constructor
29  */
30 cvox.SpeechRuleEngine = function() {
31   /**
32    * The currently active speech rule store.
33    * @type {cvox.BaseRuleStore}
34    * @private
35    */
36   this.activeStore_ = null;
38   /**
39    * Dynamic constraint annotation.
40    * @type {!cvox.SpeechRule.DynamicCstr}
41    */
42   this.dynamicCstr = {};
43   this.dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE] = 'short';
45 goog.addSingletonGetter(cvox.SpeechRuleEngine);
48 /**
49  * Parameterizes the speech rule engine.
50  * @param {cvox.BaseRuleStore} store A speech rule store.
51  */
52 cvox.SpeechRuleEngine.prototype.parameterize = function(store) {
53   try {
54     store.initialize();
55   } catch (err) {
56     if (err.name == 'StoreError') {
57       console.log('Store Error:', err.message);
58     }
59     else {
60       throw err;
61     }
62   }
63   this.activeStore_ = store;
67 /**
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.
72  */
73 cvox.SpeechRuleEngine.prototype.setDynamicConstraint = function(dynamic) {
74   if (dynamic) {
75     this.dynamicCstr = dynamic;
76   }
80 /**
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.
86  */
87 cvox.SpeechRuleEngine.prototype.constructString = function(node, expr) {
88   if (!expr) {
89     return '';
90   }
91   if (expr.charAt(0) == '"') {
92     return expr.slice(1, -1);
93   }
94   var func = this.activeStore_.customStrings.lookup(expr);
95   if (func) {
96     // We always return the result of the custom function, in case it
97     // deliberately computes the empty string!
98     return func(node);
99   }
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
109  * no node is given.
110  * @param {Node} node The node to be evaluated.
111  * @return {!Array<cvox.NavDescription>} A list of navigation descriptions for
112  *   that node.
113  */
114 cvox.SpeechRuleEngine.prototype.evaluateNode = function(node) {
115   if (!node) {
116     return [];
117   }
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.
126  * @private
127  */
128 cvox.SpeechRuleEngine.prototype.evaluateTree_ = function(node) {
129   var rule = this.activeStore_.lookupRule(node, this.dynamicCstr);
130   if (!rule) {
131     return this.activeStore_.evaluateDefault(node);
132   }
133   var components = rule.action.components;
134   var result = [];
135   for (var i = 0, component; component = components[i]; i++) {
136     var navs = [];
137     var content = component['content'] || '';
138     switch (component.type) {
139       case cvox.SpeechRule.Type.NODE:
140         var selected = this.activeStore_.applyQuery(node, content);
141         if (selected) {
142           navs = this.evaluateTree_(selected);
143         }
144         break;
145       case cvox.SpeechRule.Type.MULTI:
146         selected = this.activeStore_.applySelector(node, content);
147         if (selected.length > 0) {
148           navs = this.evaluateNodeList_(
149               selected,
150               component['sepFunc'],
151               this.constructString(node, component['separator']),
152               component['ctxtFunc'],
153               this.constructString(node, component['context']));
154         }
155         break;
156       case cvox.SpeechRule.Type.TEXT:
157         selected = this.constructString(node, content);
158         if (selected) {
159           navs = [new cvox.NavDescription({text: selected})];
160         }
161         break;
162       case cvox.SpeechRule.Type.PERSONALITY:
163       default:
164         navs = [new cvox.NavDescription({text: content})];
165     }
166     // Adding overall context if it exists.
167     if (navs[0] && component['context'] &&
168         component.type != cvox.SpeechRule.Type.MULTI) {
169       navs[0]['context'] =
170           this.constructString(node, component['context']) +
171               (navs[0]['context'] || '');
172     }
173     // Adding personality to the nav descriptions.
174     result = result.concat(this.addPersonality_(navs, component));
175   }
176   return result;
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.
192  * @private
193  */
194 cvox.SpeechRuleEngine.prototype.evaluateNodeList_ = function(
195     nodes, sepFunc, separator, ctxtFunc, context) {
196   if (nodes == []) {
197     return [];
198   }
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;};
205   var result = [];
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();
213         if (text) {
214           result.push(new cvox.NavDescription({text: text}));
215         }
216       }
217     }
218   }
219   return result;
224  * Maps properties in speech rules to personality properties.
225  * @type {{pitch : string,
226  *         rate: string,
227  *         volume: string,
228  *         pause: string}}
229  * @const
230  */
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
235                                 };
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
243  * format.
244  * @return {Array<cvox.NavDescription>} The modified array.
245  * @private
246  */
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]);
251     if (!isNaN(value)) {
252       personality[cvox.SpeechRuleEngine.propMap[key]] = value;
253     }
254   }
255   navs.forEach(goog.bind(function(nav) {
256     this.addRelativePersonality_(nav, personality);
257     this.resetPersonality_(nav);
258   }, this));
259   return navs;
264  * Adds relative personality entries to the personality of a Navigation
265  * Description.
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.
269  * @private
270  */
271 cvox.SpeechRuleEngine.prototype.addRelativePersonality_ = function(
272     nav, personality) {
273   if (!nav['personality']) {
274     nav['personality'] = personality;
275     return nav;
276   }
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];
285     } else {
286       navPersonality[p] = personality[p];
287     }
288   }
289   return nav;
294  * Resets personalities to default values if necessary.
295  * @param {cvox.NavDescription|cvox.NavMathDescription} nav Nav Description.
296  * @private
297  */
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);
302     }
303   }
308  * Flag for the debug mode of the speech rule engine.
309  * @type {boolean}
310  */
311 cvox.SpeechRuleEngine.debugMode = false;
315  * Give debug output.
316  * @param {...*} output Rest elements of debug output.
317  */
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));
323   }
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
330  *     engine.
331  */
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();}).
335     join('\n');
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.
343  */
344 cvox.SpeechRuleEngine.debugSpeechRule = function(rule, node) {
345   var store = cvox.SpeechRuleEngine.getInstance().activeStore_;
346   if (store) {
347     var prec = rule.precondition;
348     cvox.SpeechRuleEngine.outputDebug(
349         prec.query, store.applyQuery(node, prec.query));
350     prec.constraints.forEach(
351         function(cstr) {
352           cvox.SpeechRuleEngine.outputDebug(
353               cstr, store.applyConstraint(node, cstr));});
354   }
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.
362  */
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);
370   }