speech_rule_engine.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.SpeechRuleEngine = void 0;
  4. const auditory_description_js_1 = require("../audio/auditory_description.js");
  5. const span_js_1 = require("../audio/span.js");
  6. const debugger_js_1 = require("../common/debugger.js");
  7. const DomUtil = require("../common/dom_util.js");
  8. const engine_js_1 = require("../common/engine.js");
  9. const EngineConst = require("../common/engine_const.js");
  10. const xpath_util_js_1 = require("../common/xpath_util.js");
  11. const SpeechRules = require("../speech_rules/speech_rules.js");
  12. const SpeechRuleStores = require("../speech_rules/speech_rule_stores.js");
  13. const braille_store_js_1 = require("./braille_store.js");
  14. const dynamic_cstr_js_1 = require("./dynamic_cstr.js");
  15. const grammar_js_1 = require("./grammar.js");
  16. const math_store_js_1 = require("./math_store.js");
  17. const speech_rule_js_1 = require("./speech_rule.js");
  18. const trie_js_1 = require("../indexing/trie.js");
  19. class SpeechRuleEngine {
  20. static getInstance() {
  21. SpeechRuleEngine.instance =
  22. SpeechRuleEngine.instance || new SpeechRuleEngine();
  23. return SpeechRuleEngine.instance;
  24. }
  25. static debugSpeechRule(rule, node) {
  26. const prec = rule.precondition;
  27. const queryResult = rule.context.applyQuery(node, prec.query);
  28. debugger_js_1.Debugger.getInstance().output(prec.query, queryResult ? queryResult.toString() : queryResult);
  29. prec.constraints.forEach((cstr) => debugger_js_1.Debugger.getInstance().output(cstr, rule.context.applyConstraint(node, cstr)));
  30. }
  31. static debugNamedSpeechRule(name, node) {
  32. const rules = SpeechRuleEngine.getInstance().trie.collectRules();
  33. const allRules = rules.filter((rule) => rule.name == name);
  34. for (let i = 0, rule; (rule = allRules[i]); i++) {
  35. debugger_js_1.Debugger.getInstance().output('Rule', name, 'DynamicCstr:', rule.dynamicCstr.toString(), 'number', i);
  36. SpeechRuleEngine.debugSpeechRule(rule, node);
  37. }
  38. }
  39. evaluateNode(node) {
  40. (0, xpath_util_js_1.updateEvaluator)(node);
  41. const timeIn = new Date().getTime();
  42. let result = [];
  43. try {
  44. result = this.evaluateNode_(node);
  45. }
  46. catch (err) {
  47. console.log(err);
  48. console.error('Something went wrong computing speech.');
  49. debugger_js_1.Debugger.getInstance().output(err);
  50. }
  51. const timeOut = new Date().getTime();
  52. debugger_js_1.Debugger.getInstance().output('Time:', timeOut - timeIn);
  53. return result;
  54. }
  55. toString() {
  56. const allRules = this.trie.collectRules();
  57. return allRules.map((rule) => rule.toString()).join('\n');
  58. }
  59. runInSetting(settings, callback) {
  60. const engine = engine_js_1.Engine.getInstance();
  61. const save = {};
  62. for (const [key, val] of Object.entries(settings)) {
  63. save[key] = engine[key];
  64. engine[key] = val;
  65. }
  66. engine.setDynamicCstr();
  67. const result = callback();
  68. for (const [key, val] of Object.entries(save)) {
  69. engine[key] = val;
  70. }
  71. engine.setDynamicCstr();
  72. return result;
  73. }
  74. static addStore(set) {
  75. const store = storeFactory(set);
  76. if (store.kind !== 'abstract') {
  77. store
  78. .getSpeechRules()
  79. .forEach((x) => SpeechRuleEngine.getInstance().trie.addRule(x));
  80. }
  81. SpeechRuleEngine.getInstance().addEvaluator(store);
  82. }
  83. processGrammar(context, node, grammar) {
  84. const assignment = {};
  85. for (const [key, val] of Object.entries(grammar)) {
  86. assignment[key] =
  87. typeof val === 'string' ? context.constructString(node, val) : val;
  88. }
  89. grammar_js_1.Grammar.getInstance().pushState(assignment);
  90. }
  91. addEvaluator(store) {
  92. const fun = store.evaluateDefault.bind(store);
  93. const loc = this.evaluators_[store.locale];
  94. if (loc) {
  95. loc[store.modality] = fun;
  96. return;
  97. }
  98. const mod = {};
  99. mod[store.modality] = fun;
  100. this.evaluators_[store.locale] = mod;
  101. }
  102. getEvaluator(locale, modality) {
  103. const loc = this.evaluators_[locale] ||
  104. this.evaluators_[dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.LOCALE]];
  105. return loc[modality] || loc[dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY]];
  106. }
  107. enumerate(opt_info) {
  108. return this.trie.enumerate(opt_info);
  109. }
  110. constructor() {
  111. this.trie = null;
  112. this.evaluators_ = {};
  113. this.trie = new trie_js_1.Trie();
  114. }
  115. evaluateNode_(node) {
  116. if (!node) {
  117. return [];
  118. }
  119. this.updateConstraint_();
  120. let result = this.evaluateTree_(node);
  121. result = processAnnotations(result);
  122. return result;
  123. }
  124. evaluateTree_(node) {
  125. const engine = engine_js_1.Engine.getInstance();
  126. let result;
  127. debugger_js_1.Debugger.getInstance().output(engine.mode !== EngineConst.Mode.HTTP ? node.toString() : node);
  128. grammar_js_1.Grammar.getInstance().setAttribute(node);
  129. const rule = this.lookupRule(node, engine.dynamicCstr);
  130. if (!rule) {
  131. if (engine.strict) {
  132. return [];
  133. }
  134. result = this.getEvaluator(engine.locale, engine.modality)(node);
  135. if (node.attributes) {
  136. this.addPersonality_(result, {}, false, node);
  137. }
  138. return result;
  139. }
  140. debugger_js_1.Debugger.getInstance().generateOutput(() => [
  141. 'Apply Rule:',
  142. rule.name,
  143. rule.dynamicCstr.toString(),
  144. engine.mode === EngineConst.Mode.HTTP
  145. ? DomUtil.serializeXml(node)
  146. : node.toString()
  147. ]);
  148. grammar_js_1.Grammar.getInstance().processSingles();
  149. const context = rule.context;
  150. const components = rule.action.components;
  151. result = [];
  152. for (let i = 0, component; (component = components[i]); i++) {
  153. let descrs = [];
  154. const content = component.content || '';
  155. const attributes = component.attributes || {};
  156. let multi = false;
  157. if (component.grammar) {
  158. this.processGrammar(context, node, component.grammar);
  159. }
  160. let saveEngine = null;
  161. if (attributes.engine) {
  162. saveEngine = engine_js_1.Engine.getInstance().dynamicCstr.getComponents();
  163. const features = Object.assign({}, saveEngine, grammar_js_1.Grammar.parseInput(attributes.engine));
  164. engine_js_1.Engine.getInstance().setDynamicCstr(features);
  165. this.updateConstraint_();
  166. }
  167. switch (component.type) {
  168. case speech_rule_js_1.ActionType.NODE:
  169. {
  170. const selected = context.applyQuery(node, content);
  171. if (selected) {
  172. descrs = this.evaluateTree_(selected);
  173. }
  174. }
  175. break;
  176. case speech_rule_js_1.ActionType.MULTI:
  177. {
  178. multi = true;
  179. const selects = context.applySelector(node, content);
  180. if (selects.length > 0) {
  181. descrs = this.evaluateNodeList_(context, selects, attributes['sepFunc'], context.constructString(node, attributes['separator']), attributes['ctxtFunc'], context.constructString(node, attributes['context']));
  182. }
  183. }
  184. break;
  185. case speech_rule_js_1.ActionType.TEXT:
  186. {
  187. const xpath = attributes['span'];
  188. let attrs = {};
  189. if (xpath) {
  190. const nodes = (0, xpath_util_js_1.evalXPath)(xpath, node);
  191. attrs = nodes.length
  192. ? span_js_1.Span.getAttributes(nodes[0])
  193. : { kind: xpath };
  194. }
  195. const str = context.constructSpan(node, content, attrs);
  196. descrs = str.map(function (span) {
  197. return auditory_description_js_1.AuditoryDescription.create({ text: span.speech, attributes: span.attributes }, { adjust: true });
  198. });
  199. }
  200. break;
  201. case speech_rule_js_1.ActionType.PERSONALITY:
  202. default:
  203. descrs = [auditory_description_js_1.AuditoryDescription.create({ text: content })];
  204. }
  205. if (descrs[0] && !multi) {
  206. if (attributes['context']) {
  207. descrs[0]['context'] =
  208. context.constructString(node, attributes['context']) +
  209. (descrs[0]['context'] || '');
  210. }
  211. if (attributes['annotation']) {
  212. descrs[0]['annotation'] = attributes['annotation'];
  213. }
  214. }
  215. this.addLayout(descrs, attributes, multi);
  216. if (component.grammar) {
  217. grammar_js_1.Grammar.getInstance().popState();
  218. }
  219. result = result.concat(this.addPersonality_(descrs, attributes, multi, node));
  220. if (saveEngine) {
  221. engine_js_1.Engine.getInstance().setDynamicCstr(saveEngine);
  222. this.updateConstraint_();
  223. }
  224. }
  225. grammar_js_1.Grammar.getInstance().popState();
  226. return result;
  227. }
  228. evaluateNodeList_(context, nodes, sepFunc, sepStr, ctxtFunc, ctxtStr) {
  229. if (!nodes.length) {
  230. return [];
  231. }
  232. const sep = sepStr || '';
  233. const cont = ctxtStr || '';
  234. const cFunc = context.contextFunctions.lookup(ctxtFunc);
  235. const ctxtClosure = cFunc
  236. ? cFunc(nodes, cont)
  237. : function () {
  238. return cont;
  239. };
  240. const sFunc = context.contextFunctions.lookup(sepFunc);
  241. const sepClosure = sFunc
  242. ? sFunc(nodes, sep)
  243. : function () {
  244. return [
  245. auditory_description_js_1.AuditoryDescription.create({ text: sep }, { translate: true })
  246. ];
  247. };
  248. let result = [];
  249. for (let i = 0, node; (node = nodes[i]); i++) {
  250. const descrs = this.evaluateTree_(node);
  251. if (descrs.length > 0) {
  252. descrs[0]['context'] = ctxtClosure() + (descrs[0]['context'] || '');
  253. result = result.concat(descrs);
  254. if (i < nodes.length - 1) {
  255. const text = sepClosure();
  256. result = result.concat(text);
  257. }
  258. }
  259. }
  260. return result;
  261. }
  262. addLayout(descrs, props, _multi) {
  263. const layout = props.layout;
  264. if (!layout) {
  265. return;
  266. }
  267. if (layout.match(/^begin/)) {
  268. descrs.unshift(new auditory_description_js_1.AuditoryDescription({ text: '', layout: layout }));
  269. return;
  270. }
  271. if (layout.match(/^end/)) {
  272. descrs.push(new auditory_description_js_1.AuditoryDescription({ text: '', layout: layout }));
  273. return;
  274. }
  275. descrs.unshift(new auditory_description_js_1.AuditoryDescription({ text: '', layout: `begin${layout}` }));
  276. descrs.push(new auditory_description_js_1.AuditoryDescription({ text: '', layout: `end${layout}` }));
  277. }
  278. addPersonality_(descrs, props, multi, node) {
  279. const personality = {};
  280. let pause = null;
  281. for (const key of EngineConst.personalityPropList) {
  282. const value = props[key];
  283. if (typeof value === 'undefined') {
  284. continue;
  285. }
  286. const numeral = parseFloat(value);
  287. const realValue = isNaN(numeral)
  288. ? value.charAt(0) === '"'
  289. ? value.slice(1, -1)
  290. : value
  291. : numeral;
  292. if (key === EngineConst.personalityProps.PAUSE) {
  293. pause = realValue;
  294. }
  295. else {
  296. personality[key] = realValue;
  297. }
  298. }
  299. for (let i = 0, descr; (descr = descrs[i]); i++) {
  300. this.addRelativePersonality_(descr, personality);
  301. this.addExternalAttributes_(descr, node);
  302. }
  303. if (multi && descrs.length) {
  304. delete descrs[descrs.length - 1].personality[EngineConst.personalityProps.JOIN];
  305. }
  306. if (pause && descrs.length) {
  307. const last = descrs[descrs.length - 1];
  308. if (last.text || Object.keys(last.personality).length) {
  309. descrs.push(auditory_description_js_1.AuditoryDescription.create({
  310. text: '',
  311. personality: { pause: pause }
  312. }));
  313. }
  314. else {
  315. last.personality[EngineConst.personalityProps.PAUSE] = pause;
  316. }
  317. }
  318. return descrs;
  319. }
  320. addExternalAttributes_(descr, node) {
  321. if (descr.attributes['id'] === undefined) {
  322. descr.attributes['id'] = node.getAttribute('id');
  323. }
  324. if (node.hasAttributes()) {
  325. const attrs = node.attributes;
  326. for (let i = attrs.length - 1; i >= 0; i--) {
  327. const key = attrs[i].name;
  328. if (!descr.attributes[key] && key.match(/^ext/)) {
  329. descr.attributes[key] = attrs[i].value;
  330. }
  331. }
  332. }
  333. }
  334. addRelativePersonality_(descr, personality) {
  335. if (!descr['personality']) {
  336. descr['personality'] = personality;
  337. return descr;
  338. }
  339. const descrPersonality = descr['personality'];
  340. for (const [key, val] of Object.entries(personality)) {
  341. if (descrPersonality[key] &&
  342. typeof descrPersonality[key] == 'number' &&
  343. typeof val == 'number') {
  344. descrPersonality[key] = (descrPersonality[key] + val).toString();
  345. }
  346. else if (!descrPersonality[key]) {
  347. descrPersonality[key] = val;
  348. }
  349. }
  350. return descr;
  351. }
  352. updateConstraint_() {
  353. const dynamic = engine_js_1.Engine.getInstance().dynamicCstr;
  354. const strict = engine_js_1.Engine.getInstance().strict;
  355. const trie = this.trie;
  356. const props = {};
  357. let locale = dynamic.getValue(dynamic_cstr_js_1.Axis.LOCALE);
  358. let modality = dynamic.getValue(dynamic_cstr_js_1.Axis.MODALITY);
  359. let domain = dynamic.getValue(dynamic_cstr_js_1.Axis.DOMAIN);
  360. if (!trie.hasSubtrie([locale, modality, domain])) {
  361. domain = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.DOMAIN];
  362. if (!trie.hasSubtrie([locale, modality, domain])) {
  363. modality = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY];
  364. if (!trie.hasSubtrie([locale, modality, domain])) {
  365. locale = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.LOCALE];
  366. }
  367. }
  368. }
  369. props[dynamic_cstr_js_1.Axis.LOCALE] = [locale];
  370. props[dynamic_cstr_js_1.Axis.MODALITY] = [
  371. modality !== 'summary'
  372. ? modality
  373. : dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY]
  374. ];
  375. props[dynamic_cstr_js_1.Axis.DOMAIN] = [
  376. modality !== 'speech' ? dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.DOMAIN] : domain
  377. ];
  378. const order = dynamic.getOrder();
  379. for (let i = 0, axis; (axis = order[i]); i++) {
  380. if (!props[axis]) {
  381. const value = dynamic.getValue(axis);
  382. const valueSet = this.makeSet_(value, dynamic.preference);
  383. const def = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[axis];
  384. if (!strict && value !== def) {
  385. valueSet.push(def);
  386. }
  387. props[axis] = valueSet;
  388. }
  389. }
  390. dynamic.updateProperties(props);
  391. }
  392. makeSet_(value, preferences) {
  393. if (!preferences || !Object.keys(preferences).length) {
  394. return [value];
  395. }
  396. return value.split(':');
  397. }
  398. lookupRule(node, dynamic) {
  399. if (!node ||
  400. (node.nodeType !== DomUtil.NodeType.ELEMENT_NODE &&
  401. node.nodeType !== DomUtil.NodeType.TEXT_NODE)) {
  402. return null;
  403. }
  404. const matchingRules = this.lookupRules(node, dynamic);
  405. return matchingRules.length > 0
  406. ? this.pickMostConstraint_(dynamic, matchingRules)
  407. : null;
  408. }
  409. lookupRules(node, dynamic) {
  410. return this.trie.lookupRules(node, dynamic.allProperties());
  411. }
  412. pickMostConstraint_(_dynamic, rules) {
  413. const comparator = engine_js_1.Engine.getInstance().comparator;
  414. rules.sort(function (r1, r2) {
  415. return (comparator.compare(r1.dynamicCstr, r2.dynamicCstr) ||
  416. r2.precondition.priority - r1.precondition.priority ||
  417. r2.precondition.constraints.length -
  418. r1.precondition.constraints.length ||
  419. r2.precondition.rank - r1.precondition.rank);
  420. });
  421. debugger_js_1.Debugger.getInstance().generateOutput((() => {
  422. return rules.map((x) => x.name + '(' + x.dynamicCstr.toString() + ')');
  423. }).bind(this));
  424. return rules[0];
  425. }
  426. }
  427. exports.SpeechRuleEngine = SpeechRuleEngine;
  428. const stores = new Map();
  429. function getStore(locale, modality) {
  430. if (modality === 'braille' && locale === 'euro') {
  431. return new braille_store_js_1.EuroStore();
  432. }
  433. if (modality === 'braille') {
  434. return new braille_store_js_1.BrailleStore();
  435. }
  436. return new math_store_js_1.MathStore();
  437. }
  438. function storeFactory(set) {
  439. const name = `${set.locale}.${set.modality}.${set.domain}`;
  440. if (set.kind === 'actions') {
  441. const store = stores.get(name);
  442. store.parse(set);
  443. return store;
  444. }
  445. SpeechRuleStores.init();
  446. if (set && !set.functions) {
  447. set.functions = SpeechRules.getStore(set.locale, set.modality, set.domain);
  448. }
  449. const store = getStore(set.locale, set.modality);
  450. stores.set(name, store);
  451. if (set.inherits) {
  452. store.inherits = stores.get(`${set.inherits}.${set.modality}.${set.domain}`);
  453. }
  454. store.parse(set);
  455. store.initialize();
  456. return store;
  457. }
  458. engine_js_1.Engine.nodeEvaluator = SpeechRuleEngine.getInstance().evaluateNode.bind(SpeechRuleEngine.getInstance());
  459. const punctuationMarks = ['⠆', '⠒', '⠲', '⠦', '⠴', '⠄'];
  460. function processAnnotations(descrs) {
  461. const alist = new auditory_description_js_1.AuditoryList(descrs);
  462. for (const item of alist.annotations) {
  463. const descr = item.data;
  464. if (descr.annotation === 'punctuation') {
  465. const prev = alist.prevText(item);
  466. if (!prev)
  467. continue;
  468. const last = prev.data;
  469. if (last.annotation !== 'punctuation' &&
  470. last.text !== '⠀' &&
  471. descr.text.length === 1 &&
  472. punctuationMarks.indexOf(descr.text) !== -1) {
  473. descr.text = '⠸' + descr.text;
  474. }
  475. }
  476. }
  477. return alist.toList();
  478. }