abstract_walker.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import { AuditoryDescription } from '../audio/auditory_description.js';
  2. import * as AuralRendering from '../audio/aural_rendering.js';
  3. import * as DomUtil from '../common/dom_util.js';
  4. import { setup as EngineSetup } from '../common/engine_setup.js';
  5. import { KeyCode } from '../common/event_util.js';
  6. import { Attribute } from '../enrich_mathml/enrich_attr.js';
  7. import { LOCALE } from '../l10n/locale.js';
  8. import { Grammar } from '../rule_engine/grammar.js';
  9. import { SemanticSkeleton } from '../semantic_tree/semantic_skeleton.js';
  10. import * as SpeechGeneratorFactory from '../speech_generator/speech_generator_factory.js';
  11. import * as SpeechGeneratorUtil from '../speech_generator/speech_generator_util.js';
  12. import { Focus } from './focus.js';
  13. import { RebuildStree } from './rebuild_stree.js';
  14. import { WalkerMoves, WalkerState } from './walker.js';
  15. import * as WalkerUtil from './walker_util.js';
  16. import * as XpathUtil from '../common/xpath_util.js';
  17. export class AbstractWalker {
  18. constructor(node, generator, highlighter, xml) {
  19. this.node = node;
  20. this.generator = generator;
  21. this.highlighter = highlighter;
  22. this.modifier = false;
  23. this.keyMapping = new Map([
  24. [KeyCode.UP, this.up.bind(this)],
  25. [KeyCode.DOWN, this.down.bind(this)],
  26. [KeyCode.RIGHT, this.right.bind(this)],
  27. [KeyCode.LEFT, this.left.bind(this)],
  28. [KeyCode.TAB, this.repeat.bind(this)],
  29. [KeyCode.DASH, this.expand.bind(this)],
  30. [KeyCode.SPACE, this.depth.bind(this)],
  31. [KeyCode.HOME, this.home.bind(this)],
  32. [KeyCode.X, this.summary.bind(this)],
  33. [KeyCode.Z, this.detail.bind(this)],
  34. [KeyCode.V, this.virtualize.bind(this)],
  35. [KeyCode.P, this.previous.bind(this)],
  36. [KeyCode.U, this.undo.bind(this)],
  37. [KeyCode.LESS, this.previousRules.bind(this)],
  38. [KeyCode.GREATER, this.nextRules.bind(this)]
  39. ]);
  40. this.cursors = [];
  41. this.xml_ = null;
  42. this.rebuilt_ = null;
  43. this.focus_ = null;
  44. this.active_ = false;
  45. if (this.node.id) {
  46. this.id = this.node.id;
  47. }
  48. else if (this.node.hasAttribute(AbstractWalker.SRE_ID_ATTR)) {
  49. this.id = this.node.getAttribute(AbstractWalker.SRE_ID_ATTR);
  50. }
  51. else {
  52. this.node.setAttribute(AbstractWalker.SRE_ID_ATTR, AbstractWalker.ID_COUNTER.toString());
  53. this.id = AbstractWalker.ID_COUNTER++;
  54. }
  55. this.rootNode = WalkerUtil.getSemanticRoot(node);
  56. this.rootId = this.rootNode.getAttribute(Attribute.ID);
  57. this.xmlString_ = xml;
  58. this.moved = WalkerMoves.ENTER;
  59. }
  60. getXml() {
  61. if (!this.xml_) {
  62. this.xml_ = DomUtil.parseInput(this.xmlString_);
  63. }
  64. return this.xml_;
  65. }
  66. getRebuilt() {
  67. if (!this.rebuilt_) {
  68. this.rebuildStree();
  69. }
  70. return this.rebuilt_;
  71. }
  72. isActive() {
  73. return this.active_;
  74. }
  75. activate() {
  76. if (this.isActive()) {
  77. return;
  78. }
  79. this.toggleActive_();
  80. }
  81. deactivate() {
  82. if (!this.isActive()) {
  83. return;
  84. }
  85. WalkerState.setState(this.id, this.primaryId());
  86. this.toggleActive_();
  87. }
  88. getFocus(update = false) {
  89. if (this.rootId === null) {
  90. this.getRebuilt();
  91. }
  92. if (!this.focus_) {
  93. this.focus_ = this.singletonFocus(this.rootId);
  94. }
  95. if (update) {
  96. this.updateFocus();
  97. }
  98. return this.focus_;
  99. }
  100. setFocus(focus) {
  101. this.focus_ = focus;
  102. }
  103. getDepth() {
  104. return this.levels.depth() - 1;
  105. }
  106. isSpeech() {
  107. return this.generator.modality === Attribute.SPEECH;
  108. }
  109. focusDomNodes() {
  110. return this.getFocus().getDomNodes();
  111. }
  112. focusSemanticNodes() {
  113. return this.getFocus().getSemanticNodes();
  114. }
  115. speech() {
  116. const nodes = this.focusDomNodes();
  117. if (!nodes.length) {
  118. return '';
  119. }
  120. const special = this.specialMove();
  121. if (special !== null) {
  122. return special;
  123. }
  124. switch (this.moved) {
  125. case WalkerMoves.DEPTH:
  126. return this.depth_();
  127. case WalkerMoves.SUMMARY:
  128. return this.summary_();
  129. case WalkerMoves.DETAIL:
  130. return this.detail_();
  131. default: {
  132. const speech = [];
  133. const snodes = this.focusSemanticNodes();
  134. for (let i = 0, l = nodes.length; i < l; i++) {
  135. const node = nodes[i];
  136. const snode = snodes[i];
  137. speech.push(node
  138. ? this.generator.getSpeech(node, this.getXml(), this.node)
  139. : SpeechGeneratorUtil.recomputeMarkup(snode));
  140. }
  141. return this.mergePrefix_(speech);
  142. }
  143. }
  144. }
  145. move(key) {
  146. const direction = this.keyMapping.get(key);
  147. if (!direction) {
  148. return null;
  149. }
  150. const focus = direction();
  151. if (!focus || focus === this.getFocus()) {
  152. return false;
  153. }
  154. this.setFocus(focus);
  155. if (this.moved === WalkerMoves.HOME) {
  156. this.levels = this.initLevels();
  157. }
  158. return true;
  159. }
  160. up() {
  161. this.moved = WalkerMoves.UP;
  162. return this.getFocus();
  163. }
  164. down() {
  165. this.moved = WalkerMoves.DOWN;
  166. return this.getFocus();
  167. }
  168. left() {
  169. this.moved = WalkerMoves.LEFT;
  170. return this.getFocus();
  171. }
  172. right() {
  173. this.moved = WalkerMoves.RIGHT;
  174. return this.getFocus();
  175. }
  176. repeat() {
  177. this.moved = WalkerMoves.REPEAT;
  178. return this.getFocus().clone();
  179. }
  180. depth() {
  181. this.moved = this.isSpeech() ? WalkerMoves.DEPTH : WalkerMoves.REPEAT;
  182. return this.getFocus().clone();
  183. }
  184. home() {
  185. this.moved = WalkerMoves.HOME;
  186. const focus = this.singletonFocus(this.rootId);
  187. return focus;
  188. }
  189. getBySemanticId(id) {
  190. return WalkerUtil.getBySemanticId(this.node, id);
  191. }
  192. primaryId() {
  193. return this.getFocus().getSemanticPrimary().id.toString();
  194. }
  195. expand() {
  196. const primary = this.getFocus().getDomPrimary();
  197. const expandable = this.actionable_(primary);
  198. if (!expandable) {
  199. return this.getFocus();
  200. }
  201. this.moved = WalkerMoves.EXPAND;
  202. expandable.dispatchEvent(new Event('click'));
  203. return this.getFocus().clone();
  204. }
  205. expandable(node) {
  206. const parent = !!this.actionable_(node);
  207. return parent && node.childNodes.length === 0;
  208. }
  209. collapsible(node) {
  210. const parent = !!this.actionable_(node);
  211. return parent && node.childNodes.length > 0;
  212. }
  213. restoreState() {
  214. if (!this.highlighter) {
  215. return;
  216. }
  217. const state = WalkerState.getState(this.id);
  218. if (!state) {
  219. return;
  220. }
  221. let node = this.getRebuilt().nodeDict[state];
  222. const path = [];
  223. while (node) {
  224. path.push(node.id);
  225. node = node.parent;
  226. }
  227. path.pop();
  228. while (path.length > 0) {
  229. this.down();
  230. const id = path.pop();
  231. const focus = this.findFocusOnLevel(id);
  232. if (!focus) {
  233. break;
  234. }
  235. this.setFocus(focus);
  236. }
  237. this.moved = WalkerMoves.ENTER;
  238. }
  239. updateFocus() {
  240. this.setFocus(Focus.factory(this.getFocus().getSemanticPrimary().id.toString(), this.getFocus()
  241. .getSemanticNodes()
  242. .map((x) => x.id.toString()), this.getRebuilt(), this.node));
  243. }
  244. rebuildStree() {
  245. this.rebuilt_ = new RebuildStree(this.getXml());
  246. this.rootId = this.rebuilt_.stree.root.id.toString();
  247. this.generator.setRebuilt(this.rebuilt_);
  248. this.skeleton = SemanticSkeleton.fromTree(this.rebuilt_.stree);
  249. this.skeleton.populate();
  250. this.focus_ = this.singletonFocus(this.rootId);
  251. this.levels = this.initLevels();
  252. SpeechGeneratorUtil.connectMactions(this.node, this.getXml(), this.rebuilt_.xml);
  253. }
  254. previousLevel() {
  255. const dnode = this.getFocus().getDomPrimary();
  256. return dnode
  257. ? WalkerUtil.getAttribute(dnode, Attribute.PARENT)
  258. : this.getFocus().getSemanticPrimary().parent.id.toString();
  259. }
  260. nextLevel() {
  261. const dnode = this.getFocus().getDomPrimary();
  262. let children;
  263. let content;
  264. if (dnode) {
  265. children = WalkerUtil.splitAttribute(WalkerUtil.getAttribute(dnode, Attribute.CHILDREN));
  266. content = WalkerUtil.splitAttribute(WalkerUtil.getAttribute(dnode, Attribute.CONTENT));
  267. const type = WalkerUtil.getAttribute(dnode, Attribute.TYPE);
  268. const role = WalkerUtil.getAttribute(dnode, Attribute.ROLE);
  269. return this.combineContentChildren(type, role, content, children);
  270. }
  271. const toIds = (x) => x.id.toString();
  272. const snode = this.getRebuilt().nodeDict[this.primaryId()];
  273. children = snode.childNodes.map(toIds);
  274. content = snode.contentNodes.map(toIds);
  275. if (children.length === 0) {
  276. return [];
  277. }
  278. return this.combineContentChildren(snode.type, snode.role, content, children);
  279. }
  280. singletonFocus(id) {
  281. this.getRebuilt();
  282. const ids = this.retrieveVisuals(id);
  283. return this.focusFromId(id, ids);
  284. }
  285. retrieveVisuals(id) {
  286. if (!this.skeleton) {
  287. return [id];
  288. }
  289. const num = parseInt(id, 10);
  290. const semStree = this.skeleton.subtreeNodes(num);
  291. if (!semStree.length) {
  292. return [id];
  293. }
  294. semStree.unshift(num);
  295. const mmlStree = {};
  296. const result = [];
  297. XpathUtil.updateEvaluator(this.getXml());
  298. for (const child of semStree) {
  299. if (mmlStree[child]) {
  300. continue;
  301. }
  302. result.push(child.toString());
  303. mmlStree[child] = true;
  304. this.subtreeIds(child, mmlStree);
  305. }
  306. return result;
  307. }
  308. subtreeIds(id, nodes) {
  309. const xmlRoot = XpathUtil.evalXPath(`//*[@data-semantic-id="${id}"]`, this.getXml());
  310. const xpath = XpathUtil.evalXPath('*//@data-semantic-id', xmlRoot[0]);
  311. xpath.forEach((x) => (nodes[parseInt(x.textContent, 10)] = true));
  312. }
  313. focusFromId(id, ids) {
  314. return Focus.factory(id, ids, this.getRebuilt(), this.node);
  315. }
  316. summary() {
  317. this.moved = this.isSpeech() ? WalkerMoves.SUMMARY : WalkerMoves.REPEAT;
  318. return this.getFocus().clone();
  319. }
  320. detail() {
  321. this.moved = this.isSpeech() ? WalkerMoves.DETAIL : WalkerMoves.REPEAT;
  322. return this.getFocus().clone();
  323. }
  324. specialMove() {
  325. return null;
  326. }
  327. virtualize(opt_undo) {
  328. this.cursors.push({
  329. focus: this.getFocus(),
  330. levels: this.levels,
  331. undo: opt_undo || !this.cursors.length
  332. });
  333. this.levels = this.levels.clone();
  334. return this.getFocus().clone();
  335. }
  336. previous() {
  337. const previous = this.cursors.pop();
  338. if (!previous) {
  339. return this.getFocus();
  340. }
  341. this.levels = previous.levels;
  342. return previous.focus;
  343. }
  344. undo() {
  345. let previous;
  346. do {
  347. previous = this.cursors.pop();
  348. } while (previous && !previous.undo);
  349. if (!previous) {
  350. return this.getFocus();
  351. }
  352. this.levels = previous.levels;
  353. return previous.focus;
  354. }
  355. update(options) {
  356. EngineSetup(options).then(() => SpeechGeneratorFactory.generator('Tree').getSpeech(this.node, this.getXml()));
  357. }
  358. nextRules() {
  359. this.generator.nextRules();
  360. const options = this.generator.getOptions();
  361. if (options.modality !== 'speech') {
  362. return this.getFocus();
  363. }
  364. this.update(options);
  365. this.moved = WalkerMoves.REPEAT;
  366. return this.getFocus().clone();
  367. }
  368. previousRules() {
  369. var _a;
  370. this.generator.nextStyle((_a = this.getFocus().getSemanticPrimary()) === null || _a === void 0 ? void 0 : _a.id.toString());
  371. const options = this.generator.getOptions();
  372. if (options.modality !== 'speech') {
  373. return this.getFocus();
  374. }
  375. this.update(options);
  376. this.moved = WalkerMoves.REPEAT;
  377. return this.getFocus().clone();
  378. }
  379. refocus() {
  380. let focus = this.getFocus();
  381. let last;
  382. while (!focus.getNodes().length) {
  383. last = this.levels.peek();
  384. const up = this.up();
  385. if (!up) {
  386. break;
  387. }
  388. this.setFocus(up);
  389. focus = this.getFocus(true);
  390. }
  391. this.levels.push(last);
  392. this.setFocus(focus);
  393. }
  394. toggleActive_() {
  395. this.active_ = !this.active_;
  396. }
  397. mergePrefix_(speech, pre = []) {
  398. const prefix = this.isSpeech() ? this.prefix_() : '';
  399. if (prefix) {
  400. speech.unshift(prefix);
  401. }
  402. const postfix = this.isSpeech() ? this.postfix_() : '';
  403. if (postfix) {
  404. speech.push(postfix);
  405. }
  406. return AuralRendering.finalize(AuralRendering.merge(pre.concat(speech)));
  407. }
  408. prefix_() {
  409. const nodes = this.getFocus().getDomNodes();
  410. const snodes = this.getFocus().getSemanticNodes();
  411. return nodes[0]
  412. ? WalkerUtil.getAttribute(nodes[0], Attribute.PREFIX)
  413. : SpeechGeneratorUtil.retrievePrefix(snodes[0]);
  414. }
  415. postfix_() {
  416. const nodes = this.getFocus().getDomNodes();
  417. return nodes[0]
  418. ? WalkerUtil.getAttribute(nodes[0], Attribute.POSTFIX)
  419. : '';
  420. }
  421. depth_() {
  422. const oldDepth = Grammar.getInstance().getParameter('depth');
  423. Grammar.getInstance().setParameter('depth', true);
  424. const primary = this.getFocus().getDomPrimary();
  425. const expand = this.expandable(primary)
  426. ? LOCALE.MESSAGES.navigate.EXPANDABLE
  427. : this.collapsible(primary)
  428. ? LOCALE.MESSAGES.navigate.COLLAPSIBLE
  429. : '';
  430. const level = LOCALE.MESSAGES.navigate.LEVEL + ' ' + this.getDepth();
  431. const snodes = this.getFocus().getSemanticNodes();
  432. const prefix = SpeechGeneratorUtil.retrievePrefix(snodes[0]);
  433. const audio = [
  434. new AuditoryDescription({ text: level, personality: {} }),
  435. new AuditoryDescription({ text: prefix, personality: {} }),
  436. new AuditoryDescription({ text: expand, personality: {} })
  437. ];
  438. Grammar.getInstance().setParameter('depth', oldDepth);
  439. return AuralRendering.finalize(AuralRendering.markup(audio));
  440. }
  441. actionable_(node) {
  442. const parent = node === null || node === void 0 ? void 0 : node.parentNode;
  443. return parent && this.highlighter.isMactionNode(parent) ? parent : null;
  444. }
  445. summary_() {
  446. const sprimary = this.getFocus().getSemanticPrimary();
  447. const sid = sprimary.id.toString();
  448. const snode = this.getRebuilt().xml.getAttribute('id') === sid
  449. ? this.getRebuilt().xml
  450. : DomUtil.querySelectorAllByAttrValue(this.getRebuilt().xml, 'id', sid)[0];
  451. const summary = SpeechGeneratorUtil.retrieveSummary(snode);
  452. const speech = this.mergePrefix_([summary]);
  453. return speech;
  454. }
  455. detail_() {
  456. const sprimary = this.getFocus().getSemanticPrimary();
  457. const sid = sprimary.id.toString();
  458. const snode = this.getRebuilt().xml.getAttribute('id') === sid
  459. ? this.getRebuilt().xml
  460. : DomUtil.querySelectorAllByAttrValue(this.getRebuilt().xml, 'id', sid)[0];
  461. const oldAlt = snode.getAttribute('alternative');
  462. snode.removeAttribute('alternative');
  463. const detail = SpeechGeneratorUtil.computeMarkup(snode);
  464. const speech = this.mergePrefix_([detail]);
  465. snode.setAttribute('alternative', oldAlt);
  466. return speech;
  467. }
  468. }
  469. AbstractWalker.ID_COUNTER = 0;
  470. AbstractWalker.SRE_ID_ATTR = 'sre-explorer-id';