speech-rule-engine
Version:
A standalone speech rule engine for XML structures, based on the original engine from ChromeVox.
366 lines • 13.5 kB
JavaScript
import * as AuralRendering from '../audio/aural_rendering.js';
import * as Enrich from '../enrich_mathml/enrich.js';
import * as HighlighterFactory from '../highlighter/highlighter_factory.js';
import { LOCALE } from '../l10n/locale.js';
import * as Semantic from '../semantic_tree/semantic.js';
import * as SpeechGeneratorFactory from '../speech_generator/speech_generator_factory.js';
import * as SpeechGeneratorUtil from '../speech_generator/speech_generator_util.js';
import * as WalkerFactory from '../walker/walker_factory.js';
import * as WalkerUtil from '../walker/walker_util.js';
import { RebuildStree } from '../walker/rebuild_stree.js';
import * as DomUtil from './dom_util.js';
import { Engine, SREError } from './engine.js';
import * as EngineConst from '../common/engine_const.js';
import { Processor, KeyProcessor } from './processor.js';
import * as XpathUtil from './xpath_util.js';
import { SemanticSkeleton } from '../semantic_tree/semantic_skeleton.js';
const PROCESSORS = new Map();
function set(processor) {
PROCESSORS.set(processor.name, processor);
}
function get(name) {
const processor = PROCESSORS.get(name);
if (!processor) {
throw new SREError('Unknown processor ' + name);
}
return processor;
}
export function process(name, expr) {
const processor = get(name);
try {
return processor.processor(expr);
}
catch (error) {
if (error instanceof SREError) {
throw error;
}
throw new SREError(`Processing error for expression\n ${expr}`);
}
}
function print(name, data) {
const processor = get(name);
return Engine.getInstance().options.pprint
? processor.pprint(data)
: processor.print(data);
}
export function output(name, expr) {
const processor = get(name);
try {
const data = processor.processor(expr);
return Engine.getInstance().options.pprint
? processor.pprint(data)
: processor.print(data);
}
catch (error) {
if (error instanceof SREError) {
throw error;
}
throw new SREError(`Processing error for expression\n ${expr}`);
}
}
export function keypress(name, expr) {
const processor = get(name);
const key = processor instanceof KeyProcessor ? processor.key(expr) : expr;
const data = processor.processor(key);
return Engine.getInstance().options.pprint
? processor.pprint(data)
: processor.print(data);
}
set(new Processor('semantic', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
return Semantic.xmlTree(mml, Engine.getInstance().options);
},
postprocessor: function (xml, _expr) {
const setting = Engine.getInstance().options.speech;
if (setting === EngineConst.Speech.NONE) {
return xml;
}
const clone = DomUtil.cloneNode(xml);
let speech = SpeechGeneratorUtil.computeMarkup(clone, true);
if (setting === EngineConst.Speech.SHALLOW) {
xml.setAttribute('speech', AuralRendering.finalize(speech));
return xml;
}
const nodesXml = XpathUtil.evalXPath('.//*[@id]', xml);
const nodesClone = XpathUtil.evalXPath('.//*[@id]', clone);
for (let i = 0, orig, node; (orig = nodesXml[i]), (node = nodesClone[i]); i++) {
speech = SpeechGeneratorUtil.computeMarkup(node);
orig.setAttribute('speech', AuralRendering.finalize(speech));
}
return xml;
},
pprint: function (tree) {
return DomUtil.formatXml(tree.toString());
}
}));
set(new Processor('speech', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
const xml = Semantic.xmlTree(mml, Engine.getInstance().options);
const descrs = SpeechGeneratorUtil.computeSpeech(xml, true);
return AuralRendering.finalize(AuralRendering.markup(descrs));
},
pprint: function (speech) {
const str = speech.toString();
return AuralRendering.isXml() ? DomUtil.formatXml(str) : str;
}
}));
set(new Processor('json', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
const stree = Semantic.getTree(mml, Engine.getInstance().options);
return stree.toJson();
},
postprocessor: function (json, expr) {
const setting = Engine.getInstance().options.speech;
if (setting === EngineConst.Speech.NONE) {
return json;
}
const mml = DomUtil.parseInput(expr);
const xml = Semantic.xmlTree(mml, Engine.getInstance().options);
const speech = SpeechGeneratorUtil.computeMarkup(xml);
if (setting === EngineConst.Speech.SHALLOW) {
json.stree.speech = AuralRendering.finalize(speech);
return json;
}
const addRec = (json) => {
const node = XpathUtil.evalXPath(`.//*[@id=${json.id}]`, xml)[0];
const speech = SpeechGeneratorUtil.computeMarkup(node);
json.speech = AuralRendering.finalize(speech);
if (json.children) {
json.children.forEach(addRec);
}
};
addRec(json.stree);
return json;
},
print: function (json) {
return JSON.stringify(json);
},
pprint: function (json) {
return JSON.stringify(json, null, 2);
}
}));
set(new Processor('vis', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
const stree = Semantic.getTree(mml, Engine.getInstance().options);
const json = stree.toJson();
json.stree = rewriteJson(json.stree);
return json;
},
print: function (json) {
return JSON.stringify(json);
},
pprint: function (json) {
return JSON.stringify(json, null, 2);
}
}));
function rewriteJson(node) {
if (!node.children) {
return node;
}
if (node.children) {
node.children.forEach((node) => node.child = true);
}
if (node.content) {
node.content.forEach((node) => node.cont = true);
}
node.children = SemanticSkeleton.combineContentChildren(node.type, node.role, node.content || [], node.children || []);
node.children.forEach((node) => rewriteJson(node));
return node;
}
set(new Processor('description', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
const xml = Semantic.xmlTree(mml, Engine.getInstance().options);
const descrs = SpeechGeneratorUtil.computeSpeech(xml, true);
return descrs;
},
print: function (descrs) {
return JSON.stringify(descrs);
},
pprint: function (descrs) {
return JSON.stringify(descrs, null, 2);
}
}));
set(new Processor('enriched', {
processor: function (expr) {
return Enrich.semanticMathmlSync(expr, Engine.getInstance().options);
},
postprocessor: function (enr, _expr) {
const root = WalkerUtil.getSemanticRoot(enr);
let generator;
switch (Engine.getInstance().options.speech) {
case EngineConst.Speech.NONE:
break;
case EngineConst.Speech.SHALLOW:
generator = SpeechGeneratorFactory.generator('Adhoc');
generator.getSpeech(root, enr);
break;
case EngineConst.Speech.DEEP:
generator = SpeechGeneratorFactory.generator('Tree');
generator.getSpeech(enr, enr);
break;
default:
break;
}
return enr;
},
pprint: function (tree) {
return DomUtil.formatXml(tree.toString());
}
}));
set(new Processor('rebuild', {
processor: function (expr) {
const rebuilt = new RebuildStree(DomUtil.parseInput(expr));
return rebuilt.stree.xml();
},
pprint: function (tree) {
return DomUtil.formatXml(tree.toString());
}
}));
set(new Processor('walker', {
processor: function (expr) {
const generator = SpeechGeneratorFactory.generator('Node');
Processor.LocalState.speechGenerator = generator;
generator.setOptions({
modality: Engine.getInstance().options.modality,
locale: Engine.getInstance().options.locale,
domain: Engine.getInstance().options.domain,
style: Engine.getInstance().options.style
});
Processor.LocalState.highlighter = HighlighterFactory.highlighter({ color: 'black' }, { color: 'white' }, { renderer: 'NativeMML' });
const node = process('enriched', expr);
const eml = print('enriched', node);
Processor.LocalState.walker = WalkerFactory.walker(Engine.getInstance().options.walker, node, generator, Processor.LocalState.highlighter, eml);
return Processor.LocalState.walker;
},
print: function (_walker) {
return Processor.LocalState.walker.speech();
}
}));
set(new KeyProcessor('move', {
processor: function (direction) {
if (!Processor.LocalState.walker) {
return null;
}
const move = Processor.LocalState.walker.move(direction);
return move === false
? AuralRendering.error(direction)
: Processor.LocalState.walker.speech();
}
}));
set(new Processor('number', {
processor: function (numb) {
const num = parseInt(numb, 10);
return isNaN(num) ? '' : LOCALE.NUMBERS.numberToWords(num);
}
}));
set(new Processor('ordinal', {
processor: function (numb) {
const num = parseInt(numb, 10);
return isNaN(num) ? '' : LOCALE.NUMBERS.wordOrdinal(num);
}
}));
set(new Processor('numericOrdinal', {
processor: function (numb) {
const num = parseInt(numb, 10);
return isNaN(num) ? '' : LOCALE.NUMBERS.numericOrdinal(num);
}
}));
set(new Processor('vulgar', {
processor: function (numb) {
const [en, den] = numb.split('/').map((x) => parseInt(x, 10));
return isNaN(en) || isNaN(den)
? ''
: process('speech', `<mfrac><mn>${en}</mn><mn>${den}</mn></mfrac>`);
}
}));
set(new Processor('latex', {
processor: function (ltx) {
if (Engine.getInstance().options.modality !== 'braille' ||
Engine.getInstance().options.locale !== 'euro') {
console.info('LaTeX input currently only works for Euro Braille output.' +
' Please use the latex-to-speech package from npm for general' +
' LaTeX input to SRE.');
}
return process('speech', `<math data-latex="${ltx}"></math>`);
}
}));
set(new Processor('rebuildStree', {
processor: function (expr) {
return new RebuildStree(DomUtil.parseInput(expr));
}
}));
set(new Processor('speechStructure', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
let sxml;
try {
const rebuilt = new RebuildStree(mml);
sxml = rebuilt.stree.xml();
}
catch (_e) {
sxml = Semantic.xmlTree(mml, Engine.getInstance().options);
}
SpeechGeneratorUtil.connectMactionSelections(mml, sxml);
return SpeechGeneratorUtil.computeSpeechStructure(sxml);
},
print: function (descrs) {
return JSON.stringify(descrs);
},
pprint: function (descrs) {
return JSON.stringify(descrs, null, 2);
}
}));
set(new Processor('workerSpeechStructure', {
processor: function (expr) {
const mml = DomUtil.parseInput(expr);
let sxml;
try {
const rebuilt = new RebuildStree(mml);
sxml = rebuilt.stree.xml();
}
catch (_e) {
sxml = Semantic.xmlTree(mml, Engine.getInstance().options);
}
Engine.getInstance().options.automark = true;
const json = {};
assembleSpeechStructure(json, mml, sxml);
return json;
},
print: function (descrs) {
return JSON.stringify(descrs);
},
pprint: function (descrs) {
return JSON.stringify(descrs, null, 2);
}
}));
export function assembleSpeechStructure(json, mml, sxml, options = {}) {
var _a;
json.options = options;
json.mactions = SpeechGeneratorUtil.connectMactionSelections(mml, sxml);
if (options.enableSpeech === false) {
return;
}
json.speech = SpeechGeneratorUtil.computeSpeechStructure(sxml);
const root = (_a = sxml.childNodes[0]) === null || _a === void 0 ? void 0 : _a.getAttribute('id');
const links = DomUtil.querySelectorAllByAttr(sxml, 'href').length;
if (links) {
const text = `${links} ${links === 1 ? 'link' : 'links'}`;
json.speech[root]['postfix-none'] = json.speech[root]['postfix-none'] ?
json.speech[root]['postfix-none'] + `, ${text}` : text;
json.speech[root]['postfix-ssml'] = json.speech[root]['postfix-ssml'] ?
json.speech[root]['postfix-ssml'] + ` <break time="250ms"/> ${text}` : text;
}
json.label = json.speech[root]['speech-none'] +
(json.speech[root]['postfix-none'] ?
`, ${json.speech[root]['postfix-none']}` : '');
json.ssml = json.speech[root]['speech-ssml'] +
(json.speech[root]['postfix-ssml'] ?
` <prosody pitch="+30%" rate="+20%"> ${json.speech[root]['postfix-ssml']} </prosody>` : '');
json.translations = Object.assign({}, LOCALE.MESSAGES.navigate);
}
//# sourceMappingURL=processor_factory.js.map