speech-rule-engine
Version:
A standalone speech rule engine for XML structures, based on the original engine from ChromeVox.
286 lines • 10.8 kB
JavaScript
import { AuditoryDescription } from '../audio/auditory_description.js';
import * as AuralRendering from '../audio/aural_rendering.js';
import * as DomUtil from '../common/dom_util.js';
import * as XpathUtil from '../common/xpath_util.js';
import { Attribute } from '../enrich_mathml/enrich_attr.js';
import { SpeechRuleEngine } from '../rule_engine/speech_rule_engine.js';
import { SemanticRole } from '../semantic_tree/semantic_meaning.js';
import { SemanticTree } from '../semantic_tree/semantic_tree.js';
import * as WalkerUtil from '../walker/walker_util.js';
import * as EngineConst from '../common/engine_const.js';
import { ClearspeakPreferences } from '../speech_rules/clearspeak_preferences.js';
import { addPreference, findPreference } from '../speech_rules/clearspeak_preference_string.js';
export function computeSpeech(xml, clear = false) {
const result = SpeechRuleEngine.getInstance().evaluateNode(xml, clear);
return result;
}
function recomputeSpeech(semantic) {
const tree = SemanticTree.fromNode(semantic);
return computeSpeech(tree.xml());
}
export function computeMarkup(tree, clear = false) {
const descrs = computeSpeech(tree, clear);
return AuralRendering.markup(descrs);
}
export function recomputeMarkup(semantic) {
const descrs = recomputeSpeech(semantic);
return AuralRendering.markup(descrs);
}
export function addSpeech(mml, semantic, snode) {
const sxml = DomUtil.querySelectorAllByAttrValue(snode, 'id', semantic.id.toString())[0];
const speech = sxml
? AuralRendering.markup(computeSpeech(sxml))
: recomputeMarkup(semantic);
mml.setAttribute(Attribute.SPEECH, speech);
}
export function addModality(mml, semantic, modality) {
const markup = recomputeMarkup(semantic);
mml.setAttribute(modality, markup);
}
export function addPrefix(mml, semantic) {
const speech = retrievePrefix(semantic);
if (speech) {
mml.setAttribute(Attribute.PREFIX, speech);
}
}
export function retrievePrefix(semantic) {
const node = computePrefixNode(semantic);
const descrs = computePrefix(node);
return AuralRendering.markup(descrs);
}
export function computePrefix(node) {
return node
? SpeechRuleEngine.getInstance().runInSetting({
modality: 'prefix',
strict: true,
speech: true
}, function () {
return SpeechRuleEngine.getInstance().evaluateNode(node);
})
: [];
}
function computePrefixNode(semantic) {
const tree = SemanticTree.fromRoot(semantic);
const nodes = XpathUtil.evalXPath('.//*[@id="' + semantic.id + '"]', tree.xml());
let node = nodes[0];
if (nodes.length > 1) {
node = nodeAtPosition(semantic, nodes) || node;
}
return node;
}
function nodeAtPosition(semantic, nodes) {
const node = nodes[0];
if (!semantic.parent) {
return node;
}
const path = [];
while (semantic) {
path.push(semantic.id);
semantic = semantic.parent;
}
const pathEquals = function (xml, path) {
while (path.length &&
path.shift().toString() === xml.getAttribute('id') &&
xml.parentNode &&
xml.parentNode.parentNode) {
xml = xml.parentNode.parentNode;
}
return !path.length;
};
for (let i = 0, xml; (xml = nodes[i]); i++) {
if (pathEquals(xml, path.slice())) {
return xml;
}
}
return node;
}
export function connectMactions(node, mml, stree) {
const mactions = DomUtil.querySelectorAll(mml, 'maction');
for (let i = 0, maction; (maction = mactions[i]); i++) {
const aid = maction.getAttribute('id');
const span = DomUtil.querySelectorAllByAttrValue(node, 'id', aid)[0];
if (!span) {
continue;
}
const lchild = maction.childNodes[1];
const mid = lchild.getAttribute(Attribute.ID);
let cspan = WalkerUtil.getBySemanticId(node, mid);
if (cspan && cspan.getAttribute(Attribute.TYPE) !== 'dummy') {
continue;
}
cspan = span.childNodes[0];
if (cspan.getAttribute('sre-highlighter-added')) {
continue;
}
const pid = lchild.getAttribute(Attribute.PARENT);
if (pid) {
cspan.setAttribute(Attribute.PARENT, pid);
}
cspan.setAttribute(Attribute.TYPE, 'dummy');
cspan.setAttribute(Attribute.ID, mid);
cspan.setAttribute('role', 'treeitem');
cspan.setAttribute('aria-level', lchild.getAttribute('aria-level'));
const cst = DomUtil.querySelectorAllByAttrValue(stree, 'id', mid)[0];
cst.setAttribute('alternative', mid);
}
}
var NeededAttributes;
(function (NeededAttributes) {
NeededAttributes["ID"] = "data-semantic-id";
NeededAttributes["PARENT"] = "data-semantic-parent";
NeededAttributes["LEVEL"] = "aria-level";
NeededAttributes["POS"] = "aria-posinset";
NeededAttributes["ROLE"] = "role";
})(NeededAttributes || (NeededAttributes = {}));
function getNeededAttributes(stree) {
const result = {};
for (const [, attr] of Object.entries(NeededAttributes)) {
result[attr] = stree.getAttribute(attr);
}
return result;
}
export function connectMactionSelections(mml, stree) {
const mactions = DomUtil.querySelectorAll(mml, 'maction');
const results = {};
for (let i = 0, maction; (maction = mactions[i]); i++) {
const selection = parseInt(maction.getAttribute('selection'));
const children = Array.from(maction.childNodes);
const semantic = children.filter((child) => child.hasAttribute(NeededAttributes.ID))[0];
const selected = children[selection - 1];
if (!semantic || semantic === selected) {
continue;
}
const mid = semantic.getAttribute(Attribute.ID);
const cst = DomUtil.querySelectorAllByAttrValue(stree, 'id', mid)[0];
cst.setAttribute('alternative', mid);
results[maction.getAttribute('id')] = getNeededAttributes(semantic);
}
return results;
}
export function connectAllMactions(mml, stree) {
const mactions = DomUtil.querySelectorAll(mml, 'maction');
for (let i = 0, maction; (maction = mactions[i]); i++) {
const lchild = maction.childNodes[1];
const mid = lchild.getAttribute(Attribute.ID);
const cst = DomUtil.querySelectorAllByAttrValue(stree, 'id', mid)[0];
cst.setAttribute('alternative', mid);
}
}
export function retrieveSummary(node, options = {}) {
const descrs = computeSummary(node, options);
return AuralRendering.markup(descrs);
}
export function computeSummary(node, options = {}) {
const preOption = options.locale ? { locale: options.locale } : {};
return node
? SpeechRuleEngine.getInstance().runInSetting(Object.assign(preOption, {
modality: 'summary',
strict: false,
speech: true
}), function () {
return SpeechRuleEngine.getInstance().evaluateNode(node);
})
: [];
}
export function computePostfix(node) {
const postfix = [];
if (node.getAttribute('role') === SemanticRole.MGLYPH) {
postfix.push(new AuditoryDescription({ text: 'image', personality: {} }));
}
if (node.hasAttribute('href')) {
postfix.push(new AuditoryDescription({ text: 'link', personality: {} }));
}
SpeechRuleEngine.getInstance().speechStructure.addNode(node, postfix, 'postfix');
return postfix;
}
export function completeModalities(structure) {
structure.completeModality('speech', computeSpeech);
structure.completeModality('prefix', computePrefix);
structure.completeModality('postfix', computePostfix);
structure.completeModality('summary', computeSummary);
}
export function computeSpeechStructure(sxml) {
computeSpeech(sxml, true);
const structure = SpeechRuleEngine.getInstance().speechStructure;
completeModalities(structure);
return structure.json(['none', 'ssml']);
}
export function computeBrailleStructure(sxml) {
computeSpeech(sxml, true);
const structure = SpeechRuleEngine.getInstance().speechStructure;
structure.completeModality('braille', computeSpeech);
return structure.json(['none']);
}
export function nextRules(options, styles = EngineConst.DOMAIN_TO_STYLES) {
var _a;
if (options.modality !== 'speech') {
return options;
}
const prefs = ClearspeakPreferences.getLocalePreferences();
if (!prefs[options.locale]) {
return options;
}
options.domain = options.domain === 'mathspeak' ? 'clearspeak' : 'mathspeak';
options.style = (_a = styles[options.domain]) !== null && _a !== void 0 ? _a : options.style;
return options;
}
export function nextStyle(node, options) {
const { modality: modality, domain: domain, style: style, locale: locale } = options;
if (modality !== 'speech') {
return style;
}
if (domain === 'mathspeak') {
const styles = ['default', 'brief', 'sbrief'];
const index = styles.indexOf(style);
if (index === -1) {
return style;
}
return index >= styles.length - 1 ? styles[0] : styles[index + 1];
}
if (domain === 'clearspeak') {
const prefs = ClearspeakPreferences.getLocalePreferences();
const loc = prefs[locale];
if (!loc) {
return 'default';
}
const smart = ClearspeakPreferences.relevantPreferences(node);
const current = findPreference(style, smart);
const options = loc[smart].map(function (x) {
return x.split('_')[1];
});
const index = options.indexOf(current);
if (index === -1) {
return style;
}
const next = index >= options.length - 1 ? options[0] : options[index + 1];
const result = addPreference(style, smart, next);
return result;
}
return style;
}
export function toStyles(options) {
var _a;
const { domain, style, domain2style } = options;
const styles = {};
if (!domain2style) {
Object.assign(styles, EngineConst.DOMAIN_TO_STYLES);
styles[domain] = style;
return styles;
}
const split = domain2style.split(',');
for (const pair of split) {
const [first, second] = pair.split(/:(.*)/);
styles[first] = second
? second
: ((_a = EngineConst.DOMAIN_TO_STYLES[first]) !== null && _a !== void 0 ? _a : 'default');
}
return styles;
}
export function fromStyles(styles) {
const strs = [];
for (const [domain, style] of Object.entries(styles)) {
strs.push(`${domain}:${style}`);
}
return strs.join(',');
}
//# sourceMappingURL=speech_generator_util.js.map