blob: 37f860bfae7ac61dfdf4d8b7358399b7f9a3812d [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview An interface definition of a speech rule.
*
* A speech rule is a data structure along with supporting methods that
* stipulates how to transform a tree structure such as XML, a browser DOM, or
* HTML into a format (usually strings) suitable for rendering by a
* text-to-speech engine.
*
* Speech rules consists of a variable number of speech rule components. Each
* component describes how to construct a single utterance. Text-to-speech
* renders the components in order.
*/
goog.provide('cvox.SpeechRule');
goog.provide('cvox.SpeechRule.Action');
goog.provide('cvox.SpeechRule.Component');
goog.provide('cvox.SpeechRule.DynamicCstr');
goog.provide('cvox.SpeechRule.Precondition');
goog.provide('cvox.SpeechRule.Type');
/**
* Creates a speech rule with precondition, actions and admin information.
* @constructor
* @param {string} name The name of the rule.
* @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraint annotations
* of the rule.
* @param {cvox.SpeechRule.Precondition} prec Precondition of the rule.
* @param {cvox.SpeechRule.Action} action Action of the speech rule.
*/
cvox.SpeechRule = function(name, dynamic, prec, action) {
/** @type {string} */
this.name = name;
/** @type {cvox.SpeechRule.DynamicCstr} */
this.dynamicCstr = dynamic;
/** @type {cvox.SpeechRule.Precondition} */
this.precondition = prec;
/** @type {cvox.SpeechRule.Action} */
this.action = action;
};
/**
*
* @override
*/
cvox.SpeechRule.prototype.toString = function() {
var cstrStrings = [];
for (var key in this.dynamicCstr) {
cstrStrings.push(this.dynamicCstr[key]);
}
return this.name + ' | ' + cstrStrings.join('.') + ' | ' +
this.precondition.toString() + ' ==> ' +
this.action.toString();
};
/**
* Mapping for types of speech rule components.
* @enum {string}
*/
cvox.SpeechRule.Type = {
NODE: 'NODE',
MULTI: 'MULTI',
TEXT: 'TEXT',
PERSONALITY: 'PERSONALITY'
};
/**
* Maps a string to a valid speech rule type.
* @param {string} str Input string.
* @return {cvox.SpeechRule.Type}
*/
cvox.SpeechRule.Type.fromString = function(str) {
switch (str) {
case '[n]': return cvox.SpeechRule.Type.NODE;
case '[m]': return cvox.SpeechRule.Type.MULTI;
case '[t]': return cvox.SpeechRule.Type.TEXT;
case '[p]': return cvox.SpeechRule.Type.PERSONALITY;
default: throw 'Parse error: ' + str;
}
};
/**
* Maps a speech rule type to a human-readable string.
* @param {cvox.SpeechRule.Type} speechType
* @return {string} Output string.
*/
cvox.SpeechRule.Type.toString = function(speechType) {
switch (speechType) {
case cvox.SpeechRule.Type.NODE: return '[n]';
case cvox.SpeechRule.Type.MULTI: return '[m]';
case cvox.SpeechRule.Type.TEXT: return '[t]';
case cvox.SpeechRule.Type.PERSONALITY: return '[p]';
default: throw 'Unknown type error: ' + speechType;
}
};
/**
* Defines a component within a speech rule.
* @param {{type: cvox.SpeechRule.Type, content: string}} kwargs The input
* component in JSON format.
* @constructor
*/
cvox.SpeechRule.Component = function(kwargs) {
/** @type {cvox.SpeechRule.Type} */
this.type = kwargs.type;
/** @type {string} */
this.content = kwargs.content;
};
/**
* Parses a valid string representation of a speech component into a Component
* object.
* @param {string} input The input string.
* @return {cvox.SpeechRule.Component} The resulting component.
*/
cvox.SpeechRule.Component.fromString = function(input) {
// The output JSON.
var output = {};
// Parse the type.
output.type = cvox.SpeechRule.Type.fromString(input.substring(0, 3));
// Prep the rest of the parsing.
var rest = input.slice(3).trimLeft();
if (!rest) {
throw new cvox.SpeechRule.OutputError('Missing content.');
}
switch (output.type) {
case cvox.SpeechRule.Type.TEXT:
if (rest[0] == '"') {
var quotedString = cvox.SpeechRule.splitString_(rest, '\\(')[0].trim();
if (quotedString.slice(-1) != '"') {
throw new cvox.SpeechRule.OutputError('Invalid string syntax.');
}
output.content = quotedString;
rest = rest.slice(quotedString.length).trim();
if (rest.indexOf('(') == -1) {
rest = '';
}
// This break is conditional. If the content is not an explicit string,
// it can be treated like node and multi type.
break;
}
case cvox.SpeechRule.Type.NODE:
case cvox.SpeechRule.Type.MULTI:
var bracket = rest.indexOf(' (');
if (bracket == -1) {
output.content = rest.trim();
rest = '';
break;
}
output.content = rest.substring(0, bracket).trim();
rest = rest.slice(bracket).trimLeft();
break;
}
output = new cvox.SpeechRule.Component(output);
if (rest) {
output.addAttributes(rest);
}
return output;
};
/**
* @override
*/
cvox.SpeechRule.Component.prototype.toString = function() {
var strs = '';
strs += cvox.SpeechRule.Type.toString(this.type);
strs += this.content ? ' ' + this.content : '';
var attribs = this.getAttributes();
if (attribs.length > 0) {
strs += ' (' + attribs.join(', ') + ')';
}
return strs;
};
/**
* Adds a single attribute to the component.
* @param {string} attr String representation of an attribute.
*/
cvox.SpeechRule.Component.prototype.addAttribute = function(attr) {
var colon = attr.indexOf(':');
if (colon == -1) {
this[attr.trim()] = 'true';
} else {
this[attr.substring(0, colon).trim()] = attr.slice(colon + 1).trim();
}
};
/**
* Adds a list of attributes to the component.
* @param {string} attrs String representation of attribute list.
*/
cvox.SpeechRule.Component.prototype.addAttributes = function(attrs) {
if (attrs[0] != '(' || attrs.slice(-1) != ')') {
throw new cvox.SpeechRule.OutputError(
'Invalid attribute expression: ' + attrs);
}
var attribs = cvox.SpeechRule.splitString_(attrs.slice(1, -1), ',');
for (var i = 0; i < attribs.length; i++) {
this.addAttribute(attribs[i]);
}
};
/**
* Transforms the attributes of an object into a list of strings.
* @return {Array.<string>} List of translated attribute:value strings.
*/
cvox.SpeechRule.Component.prototype.getAttributes = function() {
var attribs = [];
for (var key in this) {
if (key != 'content' && key != 'type' && typeof(this[key]) != 'function') {
attribs.push(key + ':' + this[key]);
}
}
return attribs;
};
/**
* A speech rule is a collection of speech components.
* @param {Array.<cvox.SpeechRule.Component>} components The input rule.
* @constructor
*/
cvox.SpeechRule.Action = function(components) {
/** @type {Array.<cvox.SpeechRule.Component>} */
this.components = components;
};
/**
* Parses an input string into a speech rule class object.
* @param {string} input The input string.
* @return {cvox.SpeechRule.Action} The resulting object.
*/
cvox.SpeechRule.Action.fromString = function(input) {
var comps = cvox.SpeechRule.splitString_(input, ';')
.filter(function(x) {return x.match(/\S/);})
.map(function(x) {return x.trim();});
var newComps = [];
for (var i = 0; i < comps.length; i++) {
var comp = cvox.SpeechRule.Component.fromString(comps[i]);
if (comp) {
newComps.push(comp);
}
}
return new cvox.SpeechRule.Action(newComps);
};
/**
* @override
*/
cvox.SpeechRule.Action.prototype.toString = function() {
var comps = this.components.map(function(c) { return c.toString(); });
return comps.join('; ');
};
// TODO (sorge) Separatation of xpath expressions and custom functions.
// Also test validity of xpath expressions.
/**
* Constructs a valid precondition for a speech rule.
* @param {string} query A node selector function or xpath expression.
* @param {Array.<string>=} opt_constraints A list of constraint functions.
* @constructor
*/
cvox.SpeechRule.Precondition = function(query, opt_constraints) {
/** @type {string} */
this.query = query;
/** @type {!Array.<string>} */
this.constraints = opt_constraints || [];
};
/**
* @override
*/
cvox.SpeechRule.Precondition.prototype.toString = function() {
var constrs = this.constraints.join(', ');
return this.query + ', ' + constrs;
};
/**
* Split a string wrt. a given separator symbol while not splitting inside of a
* double quoted string. For example, splitting
* '[t] "matrix; 3 by 3"; [n] ./*[1]' with separators ';' would yield
* ['[t] "matrix; 3 by 3"', ' [n] ./*[1]'].
* @param {string} str String to be split.
* @param {string} sep Separator symbol.
* @return {Array.<string>} A list of single component strings.
* @private
*/
cvox.SpeechRule.splitString_ = function(str, sep) {
var strList = [];
var prefix = '';
while (str != '') {
var sepPos = str.search(sep);
if (sepPos == -1) {
if ((str.match(/"/g) || []).length % 2 != 0) {
throw new cvox.SpeechRule.OutputError(
'Invalid string in expression: ' + str);
}
strList.push(prefix + str);
prefix = '';
str = '';
} else if (
(str.substring(0, sepPos).match(/"/g) || []).length % 2 == 0) {
strList.push(prefix + str.substring(0, sepPos));
prefix = '';
str = str.substring(sepPos + 1);
} else {
var nextQuot = str.substring(sepPos).search('"');
if (nextQuot == -1) {
throw new cvox.SpeechRule.OutputError(
'Invalid string in expression: ' + str);
} else {
prefix = prefix + str.substring(0, sepPos + nextQuot + 1);
str = str.substring(sepPos + nextQuot + 1);
}
}
}
if (prefix) {
strList.push(prefix);
}
return strList;
};
/**
* Attributes for dynamic constraints.
* We define one default attribute as style. Speech rule stores can add other
* attributes later.
* @enum {string}
*/
cvox.SpeechRule.DynamicCstrAttrib =
{
STYLE: 'style'
};
/**
* Dynamic constraints are a means to specialize rules that can be changed
* dynamically by the user, for example by choosing different styles, etc.
* @typedef {!Object.<cvox.SpeechRule.DynamicCstrAttrib, string>}
*/
cvox.SpeechRule.DynamicCstr;
/**
* Error object for signaling parsing errors.
* @param {string} msg The error message.
* @constructor
* @extends {Error}
*/
cvox.SpeechRule.OutputError = function(msg) {
this.name = 'RuleError';
this.message = msg || '';
};
goog.inherits(cvox.SpeechRule.OutputError, Error);