1
0
Files

479 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpeechRuleEngine = void 0;
const auditory_description_js_1 = require("../audio/auditory_description.js");
const span_js_1 = require("../audio/span.js");
const debugger_js_1 = require("../common/debugger.js");
const DomUtil = require("../common/dom_util.js");
const engine_js_1 = require("../common/engine.js");
const EngineConst = require("../common/engine_const.js");
const xpath_util_js_1 = require("../common/xpath_util.js");
const SpeechRules = require("../speech_rules/speech_rules.js");
const SpeechRuleStores = require("../speech_rules/speech_rule_stores.js");
const braille_store_js_1 = require("./braille_store.js");
const dynamic_cstr_js_1 = require("./dynamic_cstr.js");
const grammar_js_1 = require("./grammar.js");
const math_store_js_1 = require("./math_store.js");
const speech_rule_js_1 = require("./speech_rule.js");
const trie_js_1 = require("../indexing/trie.js");
class SpeechRuleEngine {
static getInstance() {
SpeechRuleEngine.instance =
SpeechRuleEngine.instance || new SpeechRuleEngine();
return SpeechRuleEngine.instance;
}
static debugSpeechRule(rule, node) {
const prec = rule.precondition;
const queryResult = rule.context.applyQuery(node, prec.query);
debugger_js_1.Debugger.getInstance().output(prec.query, queryResult ? queryResult.toString() : queryResult);
prec.constraints.forEach((cstr) => debugger_js_1.Debugger.getInstance().output(cstr, rule.context.applyConstraint(node, cstr)));
}
static debugNamedSpeechRule(name, node) {
const rules = SpeechRuleEngine.getInstance().trie.collectRules();
const allRules = rules.filter((rule) => rule.name == name);
for (let i = 0, rule; (rule = allRules[i]); i++) {
debugger_js_1.Debugger.getInstance().output('Rule', name, 'DynamicCstr:', rule.dynamicCstr.toString(), 'number', i);
SpeechRuleEngine.debugSpeechRule(rule, node);
}
}
evaluateNode(node) {
(0, xpath_util_js_1.updateEvaluator)(node);
const timeIn = new Date().getTime();
let result = [];
try {
result = this.evaluateNode_(node);
}
catch (err) {
console.log(err);
console.error('Something went wrong computing speech.');
debugger_js_1.Debugger.getInstance().output(err);
}
const timeOut = new Date().getTime();
debugger_js_1.Debugger.getInstance().output('Time:', timeOut - timeIn);
return result;
}
toString() {
const allRules = this.trie.collectRules();
return allRules.map((rule) => rule.toString()).join('\n');
}
runInSetting(settings, callback) {
const engine = engine_js_1.Engine.getInstance();
const save = {};
for (const [key, val] of Object.entries(settings)) {
save[key] = engine[key];
engine[key] = val;
}
engine.setDynamicCstr();
const result = callback();
for (const [key, val] of Object.entries(save)) {
engine[key] = val;
}
engine.setDynamicCstr();
return result;
}
static addStore(set) {
const store = storeFactory(set);
if (store.kind !== 'abstract') {
store
.getSpeechRules()
.forEach((x) => SpeechRuleEngine.getInstance().trie.addRule(x));
}
SpeechRuleEngine.getInstance().addEvaluator(store);
}
processGrammar(context, node, grammar) {
const assignment = {};
for (const [key, val] of Object.entries(grammar)) {
assignment[key] =
typeof val === 'string' ? context.constructString(node, val) : val;
}
grammar_js_1.Grammar.getInstance().pushState(assignment);
}
addEvaluator(store) {
const fun = store.evaluateDefault.bind(store);
const loc = this.evaluators_[store.locale];
if (loc) {
loc[store.modality] = fun;
return;
}
const mod = {};
mod[store.modality] = fun;
this.evaluators_[store.locale] = mod;
}
getEvaluator(locale, modality) {
const loc = this.evaluators_[locale] ||
this.evaluators_[dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.LOCALE]];
return loc[modality] || loc[dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY]];
}
enumerate(opt_info) {
return this.trie.enumerate(opt_info);
}
constructor() {
this.trie = null;
this.evaluators_ = {};
this.trie = new trie_js_1.Trie();
}
evaluateNode_(node) {
if (!node) {
return [];
}
this.updateConstraint_();
let result = this.evaluateTree_(node);
result = processAnnotations(result);
return result;
}
evaluateTree_(node) {
const engine = engine_js_1.Engine.getInstance();
let result;
debugger_js_1.Debugger.getInstance().output(engine.mode !== EngineConst.Mode.HTTP ? node.toString() : node);
grammar_js_1.Grammar.getInstance().setAttribute(node);
const rule = this.lookupRule(node, engine.dynamicCstr);
if (!rule) {
if (engine.strict) {
return [];
}
result = this.getEvaluator(engine.locale, engine.modality)(node);
if (node.attributes) {
this.addPersonality_(result, {}, false, node);
}
return result;
}
debugger_js_1.Debugger.getInstance().generateOutput(() => [
'Apply Rule:',
rule.name,
rule.dynamicCstr.toString(),
engine.mode === EngineConst.Mode.HTTP
? DomUtil.serializeXml(node)
: node.toString()
]);
grammar_js_1.Grammar.getInstance().processSingles();
const context = rule.context;
const components = rule.action.components;
result = [];
for (let i = 0, component; (component = components[i]); i++) {
let descrs = [];
const content = component.content || '';
const attributes = component.attributes || {};
let multi = false;
if (component.grammar) {
this.processGrammar(context, node, component.grammar);
}
let saveEngine = null;
if (attributes.engine) {
saveEngine = engine_js_1.Engine.getInstance().dynamicCstr.getComponents();
const features = Object.assign({}, saveEngine, grammar_js_1.Grammar.parseInput(attributes.engine));
engine_js_1.Engine.getInstance().setDynamicCstr(features);
this.updateConstraint_();
}
switch (component.type) {
case speech_rule_js_1.ActionType.NODE:
{
const selected = context.applyQuery(node, content);
if (selected) {
descrs = this.evaluateTree_(selected);
}
}
break;
case speech_rule_js_1.ActionType.MULTI:
{
multi = true;
const selects = context.applySelector(node, content);
if (selects.length > 0) {
descrs = this.evaluateNodeList_(context, selects, attributes['sepFunc'], context.constructString(node, attributes['separator']), attributes['ctxtFunc'], context.constructString(node, attributes['context']));
}
}
break;
case speech_rule_js_1.ActionType.TEXT:
{
const xpath = attributes['span'];
let attrs = {};
if (xpath) {
const nodes = (0, xpath_util_js_1.evalXPath)(xpath, node);
attrs = nodes.length
? span_js_1.Span.getAttributes(nodes[0])
: { kind: xpath };
}
const str = context.constructSpan(node, content, attrs);
descrs = str.map(function (span) {
return auditory_description_js_1.AuditoryDescription.create({ text: span.speech, attributes: span.attributes }, { adjust: true });
});
}
break;
case speech_rule_js_1.ActionType.PERSONALITY:
default:
descrs = [auditory_description_js_1.AuditoryDescription.create({ text: content })];
}
if (descrs[0] && !multi) {
if (attributes['context']) {
descrs[0]['context'] =
context.constructString(node, attributes['context']) +
(descrs[0]['context'] || '');
}
if (attributes['annotation']) {
descrs[0]['annotation'] = attributes['annotation'];
}
}
this.addLayout(descrs, attributes, multi);
if (component.grammar) {
grammar_js_1.Grammar.getInstance().popState();
}
result = result.concat(this.addPersonality_(descrs, attributes, multi, node));
if (saveEngine) {
engine_js_1.Engine.getInstance().setDynamicCstr(saveEngine);
this.updateConstraint_();
}
}
grammar_js_1.Grammar.getInstance().popState();
return result;
}
evaluateNodeList_(context, nodes, sepFunc, sepStr, ctxtFunc, ctxtStr) {
if (!nodes.length) {
return [];
}
const sep = sepStr || '';
const cont = ctxtStr || '';
const cFunc = context.contextFunctions.lookup(ctxtFunc);
const ctxtClosure = cFunc
? cFunc(nodes, cont)
: function () {
return cont;
};
const sFunc = context.contextFunctions.lookup(sepFunc);
const sepClosure = sFunc
? sFunc(nodes, sep)
: function () {
return [
auditory_description_js_1.AuditoryDescription.create({ text: sep }, { translate: true })
];
};
let result = [];
for (let i = 0, node; (node = nodes[i]); i++) {
const descrs = this.evaluateTree_(node);
if (descrs.length > 0) {
descrs[0]['context'] = ctxtClosure() + (descrs[0]['context'] || '');
result = result.concat(descrs);
if (i < nodes.length - 1) {
const text = sepClosure();
result = result.concat(text);
}
}
}
return result;
}
addLayout(descrs, props, _multi) {
const layout = props.layout;
if (!layout) {
return;
}
if (layout.match(/^begin/)) {
descrs.unshift(new auditory_description_js_1.AuditoryDescription({ text: '', layout: layout }));
return;
}
if (layout.match(/^end/)) {
descrs.push(new auditory_description_js_1.AuditoryDescription({ text: '', layout: layout }));
return;
}
descrs.unshift(new auditory_description_js_1.AuditoryDescription({ text: '', layout: `begin${layout}` }));
descrs.push(new auditory_description_js_1.AuditoryDescription({ text: '', layout: `end${layout}` }));
}
addPersonality_(descrs, props, multi, node) {
const personality = {};
let pause = null;
for (const key of EngineConst.personalityPropList) {
const value = props[key];
if (typeof value === 'undefined') {
continue;
}
const numeral = parseFloat(value);
const realValue = isNaN(numeral)
? value.charAt(0) === '"'
? value.slice(1, -1)
: value
: numeral;
if (key === EngineConst.personalityProps.PAUSE) {
pause = realValue;
}
else {
personality[key] = realValue;
}
}
for (let i = 0, descr; (descr = descrs[i]); i++) {
this.addRelativePersonality_(descr, personality);
this.addExternalAttributes_(descr, node);
}
if (multi && descrs.length) {
delete descrs[descrs.length - 1].personality[EngineConst.personalityProps.JOIN];
}
if (pause && descrs.length) {
const last = descrs[descrs.length - 1];
if (last.text || Object.keys(last.personality).length) {
descrs.push(auditory_description_js_1.AuditoryDescription.create({
text: '',
personality: { pause: pause }
}));
}
else {
last.personality[EngineConst.personalityProps.PAUSE] = pause;
}
}
return descrs;
}
addExternalAttributes_(descr, node) {
if (descr.attributes['id'] === undefined) {
descr.attributes['id'] = node.getAttribute('id');
}
if (node.hasAttributes()) {
const attrs = node.attributes;
for (let i = attrs.length - 1; i >= 0; i--) {
const key = attrs[i].name;
if (!descr.attributes[key] && key.match(/^ext/)) {
descr.attributes[key] = attrs[i].value;
}
}
}
}
addRelativePersonality_(descr, personality) {
if (!descr['personality']) {
descr['personality'] = personality;
return descr;
}
const descrPersonality = descr['personality'];
for (const [key, val] of Object.entries(personality)) {
if (descrPersonality[key] &&
typeof descrPersonality[key] == 'number' &&
typeof val == 'number') {
descrPersonality[key] = (descrPersonality[key] + val).toString();
}
else if (!descrPersonality[key]) {
descrPersonality[key] = val;
}
}
return descr;
}
updateConstraint_() {
const dynamic = engine_js_1.Engine.getInstance().dynamicCstr;
const strict = engine_js_1.Engine.getInstance().strict;
const trie = this.trie;
const props = {};
let locale = dynamic.getValue(dynamic_cstr_js_1.Axis.LOCALE);
let modality = dynamic.getValue(dynamic_cstr_js_1.Axis.MODALITY);
let domain = dynamic.getValue(dynamic_cstr_js_1.Axis.DOMAIN);
if (!trie.hasSubtrie([locale, modality, domain])) {
domain = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.DOMAIN];
if (!trie.hasSubtrie([locale, modality, domain])) {
modality = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY];
if (!trie.hasSubtrie([locale, modality, domain])) {
locale = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.LOCALE];
}
}
}
props[dynamic_cstr_js_1.Axis.LOCALE] = [locale];
props[dynamic_cstr_js_1.Axis.MODALITY] = [
modality !== 'summary'
? modality
: dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY]
];
props[dynamic_cstr_js_1.Axis.DOMAIN] = [
modality !== 'speech' ? dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.DOMAIN] : domain
];
const order = dynamic.getOrder();
for (let i = 0, axis; (axis = order[i]); i++) {
if (!props[axis]) {
const value = dynamic.getValue(axis);
const valueSet = this.makeSet_(value, dynamic.preference);
const def = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[axis];
if (!strict && value !== def) {
valueSet.push(def);
}
props[axis] = valueSet;
}
}
dynamic.updateProperties(props);
}
makeSet_(value, preferences) {
if (!preferences || !Object.keys(preferences).length) {
return [value];
}
return value.split(':');
}
lookupRule(node, dynamic) {
if (!node ||
(node.nodeType !== DomUtil.NodeType.ELEMENT_NODE &&
node.nodeType !== DomUtil.NodeType.TEXT_NODE)) {
return null;
}
const matchingRules = this.lookupRules(node, dynamic);
return matchingRules.length > 0
? this.pickMostConstraint_(dynamic, matchingRules)
: null;
}
lookupRules(node, dynamic) {
return this.trie.lookupRules(node, dynamic.allProperties());
}
pickMostConstraint_(_dynamic, rules) {
const comparator = engine_js_1.Engine.getInstance().comparator;
rules.sort(function (r1, r2) {
return (comparator.compare(r1.dynamicCstr, r2.dynamicCstr) ||
r2.precondition.priority - r1.precondition.priority ||
r2.precondition.constraints.length -
r1.precondition.constraints.length ||
r2.precondition.rank - r1.precondition.rank);
});
debugger_js_1.Debugger.getInstance().generateOutput((() => {
return rules.map((x) => x.name + '(' + x.dynamicCstr.toString() + ')');
}).bind(this));
return rules[0];
}
}
exports.SpeechRuleEngine = SpeechRuleEngine;
const stores = new Map();
function getStore(locale, modality) {
if (modality === 'braille' && locale === 'euro') {
return new braille_store_js_1.EuroStore();
}
if (modality === 'braille') {
return new braille_store_js_1.BrailleStore();
}
return new math_store_js_1.MathStore();
}
function storeFactory(set) {
const name = `${set.locale}.${set.modality}.${set.domain}`;
if (set.kind === 'actions') {
const store = stores.get(name);
store.parse(set);
return store;
}
SpeechRuleStores.init();
if (set && !set.functions) {
set.functions = SpeechRules.getStore(set.locale, set.modality, set.domain);
}
const store = getStore(set.locale, set.modality);
stores.set(name, store);
if (set.inherits) {
store.inherits = stores.get(`${set.inherits}.${set.modality}.${set.domain}`);
}
store.parse(set);
store.initialize();
return store;
}
engine_js_1.Engine.nodeEvaluator = SpeechRuleEngine.getInstance().evaluateNode.bind(SpeechRuleEngine.getInstance());
const punctuationMarks = ['⠆', '⠒', '⠲', '⠦', '⠴', '⠄'];
function processAnnotations(descrs) {
const alist = new auditory_description_js_1.AuditoryList(descrs);
for (const item of alist.annotations) {
const descr = item.data;
if (descr.annotation === 'punctuation') {
const prev = alist.prevText(item);
if (!prev)
continue;
const last = prev.data;
if (last.annotation !== 'punctuation' &&
last.text !== '' &&
descr.text.length === 1 &&
punctuationMarks.indexOf(descr.text) !== -1) {
descr.text = '⠸' + descr.text;
}
}
}
return alist.toList();
}