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 Base class for all speech rule stores.
8 * The base rule store implements some basic functionality that is common to
9 * most speech rule stores.
12 goog.provide('cvox.BaseRuleStore');
14 goog.require('cvox.MathUtil');
15 goog.require('cvox.SpeechRule');
16 goog.require('cvox.SpeechRuleEvaluator');
17 goog.require('cvox.SpeechRuleFunctions');
18 goog.require('cvox.SpeechRuleStore');
23 * @implements {cvox.SpeechRuleEvaluator}
24 * @implements {cvox.SpeechRuleStore}
26 cvox.BaseRuleStore = function() {
28 * Set of custom query functions for the store.
29 * @type {cvox.SpeechRuleFunctions.CustomQueries}
31 this.customQueries = new cvox.SpeechRuleFunctions.CustomQueries();
34 * Set of custom strings for the store.
35 * @type {cvox.SpeechRuleFunctions.CustomStrings}
37 this.customStrings = new cvox.SpeechRuleFunctions.CustomStrings();
40 * Set of context functions for the store.
41 * @type {cvox.SpeechRuleFunctions.ContextFunctions}
43 this.contextFunctions = new cvox.SpeechRuleFunctions.ContextFunctions();
46 * Set of speech rules in the store.
47 * @type {!Array<cvox.SpeechRule>}
50 this.speechRules_ = [];
53 * A priority list of dynamic constraint attributes.
54 * @type {!Array<cvox.SpeechRule.DynamicCstrAttrib>}
56 this.dynamicCstrAttribs = [cvox.SpeechRule.DynamicCstrAttrib.STYLE];
59 * List of TTS properties overridden by the store when it is active.
60 * @type {!Array<string>}
62 this.defaultTtsProps = [];
69 cvox.BaseRuleStore.prototype.lookupRule = function(node, dynamic) {
71 (node.nodeType != Node.ELEMENT_NODE && node.nodeType != Node.TEXT_NODE)) {
74 var matchingRules = this.speechRules_.filter(
77 return this.testDynamicConstraints(dynamic, rule) &&
78 this.testPrecondition_(/** @type {!Node} */ (node), rule);},
80 return (matchingRules.length > 0) ?
81 this.pickMostConstraint_(dynamic, matchingRules) : null;
88 cvox.BaseRuleStore.prototype.defineRule = function(
89 name, dynamic, action, prec, cstr) {
91 var postc = cvox.SpeechRule.Action.fromString(action);
92 var cstrList = Array.prototype.slice.call(arguments, 4);
93 var fullPrec = new cvox.SpeechRule.Precondition(prec, cstrList);
95 dynamicCstr[cvox.SpeechRule.DynamicCstrAttrib.STYLE] = dynamic;
96 var rule = new cvox.SpeechRule(name, dynamicCstr, fullPrec, postc);
98 if (err.name == 'RuleError') {
99 console.log('Rule Error ', prec, '(' + dynamic + '):', err.message);
114 cvox.BaseRuleStore.prototype.addRule = function(rule) {
115 this.speechRules_.unshift(rule);
122 cvox.BaseRuleStore.prototype.deleteRule = function(rule) {
123 var index = this.speechRules_.indexOf(rule);
125 this.speechRules_.splice(index, 1);
133 cvox.BaseRuleStore.prototype.findRule = function(pred) {
134 for (var i = 0, rule; rule = this.speechRules_[i]; i++) {
146 cvox.BaseRuleStore.prototype.findAllRules = function(pred) {
147 return this.speechRules_.filter(pred);
154 cvox.BaseRuleStore.prototype.evaluateDefault = function(node) {
155 return [new cvox.NavDescription({'text': node.textContent})];
160 * Test the applicability of a speech rule in debugging mode.
161 * @param {string} name Rule to debug.
162 * @param {!Node} node DOM node to test applicability of given rule.
164 cvox.BaseRuleStore.prototype.debugSpeechRule = goog.abstractMethod;
168 * Function to initialize the store with speech rules. It is called by the
169 * speech rule engine upon parametrization with this store. The function allows
170 * us to define sets of rules in separate files while depending on functionality
171 * that is defined in the rule store.
172 * Essentially it is a way of getting around dependencies.
174 cvox.BaseRuleStore.prototype.initialize = goog.abstractMethod;
178 * Removes duplicates of the given rule from the rule store. Thereby duplicates
179 * are identified by having the same precondition and dynamic constraint.
180 * @param {cvox.SpeechRule} rule The rule.
182 cvox.BaseRuleStore.prototype.removeDuplicates = function(rule) {
183 for (var i = this.speechRules_.length - 1, oldRule;
184 oldRule = this.speechRules_[i]; i--) {
185 if (oldRule != rule &&
186 cvox.BaseRuleStore.compareDynamicConstraints_(
187 oldRule.dynamicCstr, rule.dynamicCstr) &&
188 cvox.BaseRuleStore.comparePreconditions_(oldRule, rule)) {
189 this.speechRules_.splice(i, 1);
195 // TODO (sorge) These should move into the speech rule functions.
197 * Checks if we have a custom query and applies it. Otherwise returns null.
198 * @param {!Node} node The initial node.
199 * @param {string} funcName A function name.
200 * @return {Array<Node>} The list of resulting nodes.
202 cvox.BaseRuleStore.prototype.applyCustomQuery = function(
204 var func = this.customQueries.lookup(funcName);
205 return func ? func(node) : null;
210 * Applies either an Xpath selector or a custom query to the node
211 * and returns the resulting node list.
212 * @param {!Node} node The initial node.
213 * @param {string} expr An Xpath expression string or a name of a custom
215 * @return {Array<Node>} The list of resulting nodes.
217 cvox.BaseRuleStore.prototype.applySelector = function(node, expr) {
218 var result = this.applyCustomQuery(node, expr);
219 return result || cvox.XpathUtil.evalXPath(expr, node);
224 * Applies either an Xpath selector or a custom query to the node
225 * and returns the first result.
226 * @param {!Node} node The initial node.
227 * @param {string} expr An Xpath expression string or a name of a custom
229 * @return {Node} The resulting node.
231 cvox.BaseRuleStore.prototype.applyQuery = function(node, expr) {
232 var results = this.applySelector(node, expr);
233 if (results.length > 0) {
241 * Applies either an Xpath selector or a custom query to the node and returns
242 * true if the application yields a non-empty result.
243 * @param {!Node} node The initial node.
244 * @param {string} expr An Xpath expression string or a name of a custom
246 * @return {boolean} True if application was successful.
248 cvox.BaseRuleStore.prototype.applyConstraint = function(node, expr) {
249 var result = this.applyQuery(node, expr);
250 return !!result || cvox.XpathUtil.evaluateBoolean(expr, node);
255 * Tests whether a speech rule satisfies a set of dynamic constraints.
256 * @param {!cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraints.
257 * @param {cvox.SpeechRule} rule The rule.
258 * @return {boolean} True if the preconditions apply to the node.
261 cvox.BaseRuleStore.prototype.testDynamicConstraints = function(
263 // We allow a default value for each dynamic constraints attribute.
264 // The idea is that when we can not find a speech rule matching the value for
265 // a particular attribute in the dynamic constraintwe choose the one that has
266 // the value 'default'.
267 var allKeys = /** @type {Array<cvox.SpeechRule.DynamicCstrAttrib>} */ (
268 Object.keys(dynamic));
269 return allKeys.every(
271 return dynamic[key] == rule.dynamicCstr[key] ||
272 rule.dynamicCstr[key] == 'default';
278 * Get a set of all dynamic constraint values.
279 * @return {!Object<cvox.SpeechRule.DynamicCstrAttrib, Array<string>>} The
280 * object with all annotations.
282 cvox.BaseRuleStore.prototype.getDynamicConstraintValues = function() {
284 for (var i = 0, rule; rule = this.speechRules_[i]; i++) {
285 for (var key in rule.dynamicCstr) {
286 var newKey = [rule.dynamicCstr[key]];
288 result[key] = cvox.MathUtil.union(result[key], newKey);
290 result[key] = newKey;
299 * Counts how many dynamic constraint values match exactly in the order
300 * specified by the store.
301 * @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraints.
302 * @param {cvox.SpeechRule} rule The speech rule to match.
303 * @return {number} The number of matching dynamic constraint values.
306 cvox.BaseRuleStore.prototype.countMatchingDynamicConstraintValues_ = function(
309 for (var i = 0, key; key = this.dynamicCstrAttribs[i]; i++) {
310 if (dynamic[key] == rule.dynamicCstr[key]) {
319 * Picks the result of the most constraint rule by prefering those:
320 * 1) that best match the dynamic constraints.
321 * 2) with the most additional constraints.
322 * @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraints.
323 * @param {!Array<cvox.SpeechRule>} rules An array of rules.
324 * @return {cvox.SpeechRule} The most constraint rule.
327 cvox.BaseRuleStore.prototype.pickMostConstraint_ = function(dynamic, rules) {
328 rules.sort(goog.bind(
330 var count1 = this.countMatchingDynamicConstraintValues_(dynamic, r1);
331 var count2 = this.countMatchingDynamicConstraintValues_(dynamic, r2);
332 // Rule one is a better match, don't swap.
333 if (count1 > count2) {
336 // Rule two is a better match, swap.
337 if (count2 > count1) {
340 // When same number of dynamic constraint attributes matches for
341 // both rules, compare length of static constraints.
342 return (r2.precondition.constraints.length -
343 r1.precondition.constraints.length);},
350 * Test the precondition of a speech rule.
351 * @param {!Node} node on which to test applicability of the rule.
352 * @param {cvox.SpeechRule} rule The rule to be tested.
353 * @return {boolean} True if the preconditions apply to the node.
356 cvox.BaseRuleStore.prototype.testPrecondition_ = function(node, rule) {
357 var prec = rule.precondition;
358 return this.applyQuery(node, prec.query) === node &&
359 prec.constraints.every(
360 goog.bind(function(cstr) {
361 return this.applyConstraint(node, cstr);},
366 // TODO (sorge) Define the following methods directly on the dynamic constraint
367 // and precondition classes, respectively.
369 * Compares two dynamic constraints and returns true if they are equal.
370 * @param {cvox.SpeechRule.DynamicCstr} cstr1 First dynamic constraints.
371 * @param {cvox.SpeechRule.DynamicCstr} cstr2 Second dynamic constraints.
372 * @return {boolean} True if the dynamic constraints are equal.
375 cvox.BaseRuleStore.compareDynamicConstraints_ = function(
377 if (Object.keys(cstr1).length != Object.keys(cstr2).length) {
380 for (var key in cstr1) {
381 if (!cstr2[key] || cstr1[key] !== cstr2[key]) {
390 * Compares two static constraints (i.e., lists of precondition constraints) and
391 * returns true if they are equal.
392 * @param {Array<string>} cstr1 First static constraints.
393 * @param {Array<string>} cstr2 Second static constraints.
394 * @return {boolean} True if the static constraints are equal.
397 cvox.BaseRuleStore.compareStaticConstraints_ = function(
399 if (cstr1.length != cstr2.length) {
402 for (var i = 0, cstr; cstr = cstr1[i]; i++) {
403 if (cstr2.indexOf(cstr) == -1) {
412 * Compares the preconditions of two speech rules.
413 * @param {cvox.SpeechRule} rule1 The first speech rule.
414 * @param {cvox.SpeechRule} rule2 The second speech rule.
415 * @return {boolean} True if the preconditions are equal.
418 cvox.BaseRuleStore.comparePreconditions_ = function(rule1, rule2) {
419 var prec1 = rule1.precondition;
420 var prec2 = rule2.precondition;
421 if (prec1.query != prec2.query) {
424 return cvox.BaseRuleStore.compareStaticConstraints_(
425 prec1.constraints, prec2.constraints);