UNPKG

speech-rule-engine

Version:

A standalone speech rule engine for XML structures, based on the original engine from ChromeVox.

286 lines 10.8 kB
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