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