processor_factory.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import * as AuralRendering from '../audio/aural_rendering.js';
  2. import * as Enrich from '../enrich_mathml/enrich.js';
  3. import * as HighlighterFactory from '../highlighter/highlighter_factory.js';
  4. import { LOCALE } from '../l10n/locale.js';
  5. import * as Semantic from '../semantic_tree/semantic.js';
  6. import * as SpeechGeneratorFactory from '../speech_generator/speech_generator_factory.js';
  7. import * as SpeechGeneratorUtil from '../speech_generator/speech_generator_util.js';
  8. import * as WalkerFactory from '../walker/walker_factory.js';
  9. import * as WalkerUtil from '../walker/walker_util.js';
  10. import { RebuildStree } from '../walker/rebuild_stree.js';
  11. import * as DomUtil from './dom_util.js';
  12. import { Engine, SREError } from './engine.js';
  13. import * as EngineConst from '../common/engine_const.js';
  14. import { Processor, KeyProcessor } from './processor.js';
  15. import * as XpathUtil from './xpath_util.js';
  16. const PROCESSORS = new Map();
  17. function set(processor) {
  18. PROCESSORS.set(processor.name, processor);
  19. }
  20. function get(name) {
  21. const processor = PROCESSORS.get(name);
  22. if (!processor) {
  23. throw new SREError('Unknown processor ' + name);
  24. }
  25. return processor;
  26. }
  27. export function process(name, expr) {
  28. const processor = get(name);
  29. try {
  30. return processor.processor(expr);
  31. }
  32. catch (_e) {
  33. throw new SREError('Processing error for expression ' + expr);
  34. }
  35. }
  36. function print(name, data) {
  37. const processor = get(name);
  38. return Engine.getInstance().pprint
  39. ? processor.pprint(data)
  40. : processor.print(data);
  41. }
  42. export function output(name, expr) {
  43. const processor = get(name);
  44. try {
  45. const data = processor.processor(expr);
  46. return Engine.getInstance().pprint
  47. ? processor.pprint(data)
  48. : processor.print(data);
  49. }
  50. catch (_e) {
  51. console.log(_e);
  52. throw new SREError('Processing error for expression ' + expr);
  53. }
  54. }
  55. export function keypress(name, expr) {
  56. const processor = get(name);
  57. const key = processor instanceof KeyProcessor ? processor.key(expr) : expr;
  58. const data = processor.processor(key);
  59. return Engine.getInstance().pprint
  60. ? processor.pprint(data)
  61. : processor.print(data);
  62. }
  63. set(new Processor('semantic', {
  64. processor: function (expr) {
  65. const mml = DomUtil.parseInput(expr);
  66. return Semantic.xmlTree(mml);
  67. },
  68. postprocessor: function (xml, _expr) {
  69. const setting = Engine.getInstance().speech;
  70. if (setting === EngineConst.Speech.NONE) {
  71. return xml;
  72. }
  73. const clone = DomUtil.cloneNode(xml);
  74. let speech = SpeechGeneratorUtil.computeMarkup(clone);
  75. if (setting === EngineConst.Speech.SHALLOW) {
  76. xml.setAttribute('speech', AuralRendering.finalize(speech));
  77. return xml;
  78. }
  79. const nodesXml = XpathUtil.evalXPath('.//*[@id]', xml);
  80. const nodesClone = XpathUtil.evalXPath('.//*[@id]', clone);
  81. for (let i = 0, orig, node; (orig = nodesXml[i]), (node = nodesClone[i]); i++) {
  82. speech = SpeechGeneratorUtil.computeMarkup(node);
  83. orig.setAttribute('speech', AuralRendering.finalize(speech));
  84. }
  85. return xml;
  86. },
  87. pprint: function (tree) {
  88. return DomUtil.formatXml(tree.toString());
  89. }
  90. }));
  91. set(new Processor('speech', {
  92. processor: function (expr) {
  93. const mml = DomUtil.parseInput(expr);
  94. const xml = Semantic.xmlTree(mml);
  95. const descrs = SpeechGeneratorUtil.computeSpeech(xml);
  96. return AuralRendering.finalize(AuralRendering.markup(descrs));
  97. },
  98. pprint: function (speech) {
  99. const str = speech.toString();
  100. return AuralRendering.isXml() ? DomUtil.formatXml(str) : str;
  101. }
  102. }));
  103. set(new Processor('json', {
  104. processor: function (expr) {
  105. const mml = DomUtil.parseInput(expr);
  106. const stree = Semantic.getTree(mml);
  107. return stree.toJson();
  108. },
  109. postprocessor: function (json, expr) {
  110. const setting = Engine.getInstance().speech;
  111. if (setting === EngineConst.Speech.NONE) {
  112. return json;
  113. }
  114. const mml = DomUtil.parseInput(expr);
  115. const xml = Semantic.xmlTree(mml);
  116. const speech = SpeechGeneratorUtil.computeMarkup(xml);
  117. if (setting === EngineConst.Speech.SHALLOW) {
  118. json.stree.speech = AuralRendering.finalize(speech);
  119. return json;
  120. }
  121. const addRec = (json) => {
  122. const node = XpathUtil.evalXPath(`.//*[@id=${json.id}]`, xml)[0];
  123. const speech = SpeechGeneratorUtil.computeMarkup(node);
  124. json.speech = AuralRendering.finalize(speech);
  125. if (json.children) {
  126. json.children.forEach(addRec);
  127. }
  128. };
  129. addRec(json.stree);
  130. return json;
  131. },
  132. print: function (json) {
  133. return JSON.stringify(json);
  134. },
  135. pprint: function (json) {
  136. return JSON.stringify(json, null, 2);
  137. }
  138. }));
  139. set(new Processor('description', {
  140. processor: function (expr) {
  141. const mml = DomUtil.parseInput(expr);
  142. const xml = Semantic.xmlTree(mml);
  143. const descrs = SpeechGeneratorUtil.computeSpeech(xml);
  144. return descrs;
  145. },
  146. print: function (descrs) {
  147. return JSON.stringify(descrs);
  148. },
  149. pprint: function (descrs) {
  150. return JSON.stringify(descrs, null, 2);
  151. }
  152. }));
  153. set(new Processor('enriched', {
  154. processor: function (expr) {
  155. return Enrich.semanticMathmlSync(expr);
  156. },
  157. postprocessor: function (enr, _expr) {
  158. const root = WalkerUtil.getSemanticRoot(enr);
  159. let generator;
  160. switch (Engine.getInstance().speech) {
  161. case EngineConst.Speech.NONE:
  162. break;
  163. case EngineConst.Speech.SHALLOW:
  164. generator = SpeechGeneratorFactory.generator('Adhoc');
  165. generator.getSpeech(root, enr);
  166. break;
  167. case EngineConst.Speech.DEEP:
  168. generator = SpeechGeneratorFactory.generator('Tree');
  169. generator.getSpeech(enr, enr);
  170. break;
  171. default:
  172. break;
  173. }
  174. return enr;
  175. },
  176. pprint: function (tree) {
  177. return DomUtil.formatXml(tree.toString());
  178. }
  179. }));
  180. set(new Processor('rebuild', {
  181. processor: function (expr) {
  182. const rebuilt = new RebuildStree(DomUtil.parseInput(expr));
  183. return rebuilt.stree.xml();
  184. },
  185. pprint: function (tree) {
  186. return DomUtil.formatXml(tree.toString());
  187. }
  188. }));
  189. set(new Processor('walker', {
  190. processor: function (expr) {
  191. const generator = SpeechGeneratorFactory.generator('Node');
  192. Processor.LocalState.speechGenerator = generator;
  193. generator.setOptions({
  194. modality: Engine.getInstance().modality,
  195. locale: Engine.getInstance().locale,
  196. domain: Engine.getInstance().domain,
  197. style: Engine.getInstance().style
  198. });
  199. Processor.LocalState.highlighter = HighlighterFactory.highlighter({ color: 'black' }, { color: 'white' }, { renderer: 'NativeMML' });
  200. const node = process('enriched', expr);
  201. const eml = print('enriched', node);
  202. Processor.LocalState.walker = WalkerFactory.walker(Engine.getInstance().walker, node, generator, Processor.LocalState.highlighter, eml);
  203. return Processor.LocalState.walker;
  204. },
  205. print: function (_walker) {
  206. return Processor.LocalState.walker.speech();
  207. }
  208. }));
  209. set(new KeyProcessor('move', {
  210. processor: function (direction) {
  211. if (!Processor.LocalState.walker) {
  212. return null;
  213. }
  214. const move = Processor.LocalState.walker.move(direction);
  215. return move === false
  216. ? AuralRendering.error(direction)
  217. : Processor.LocalState.walker.speech();
  218. }
  219. }));
  220. set(new Processor('number', {
  221. processor: function (numb) {
  222. const num = parseInt(numb, 10);
  223. return isNaN(num) ? '' : LOCALE.NUMBERS.numberToWords(num);
  224. }
  225. }));
  226. set(new Processor('ordinal', {
  227. processor: function (numb) {
  228. const num = parseInt(numb, 10);
  229. return isNaN(num) ? '' : LOCALE.NUMBERS.wordOrdinal(num);
  230. }
  231. }));
  232. set(new Processor('numericOrdinal', {
  233. processor: function (numb) {
  234. const num = parseInt(numb, 10);
  235. return isNaN(num) ? '' : LOCALE.NUMBERS.numericOrdinal(num);
  236. }
  237. }));
  238. set(new Processor('vulgar', {
  239. processor: function (numb) {
  240. const [en, den] = numb.split('/').map((x) => parseInt(x, 10));
  241. return isNaN(en) || isNaN(den)
  242. ? ''
  243. : process('speech', `<mfrac><mn>${en}</mn><mn>${den}</mn></mfrac>`);
  244. }
  245. }));
  246. set(new Processor('latex', {
  247. processor: function (ltx) {
  248. if (Engine.getInstance().modality !== 'braille' ||
  249. Engine.getInstance().locale !== 'euro') {
  250. console.info('LaTeX input currently only works for Euro Braille output.' +
  251. ' Please use the latex-to-speech package from npm for general' +
  252. ' LaTeX input to SRE.');
  253. }
  254. return process('speech', `<math data-latex="${ltx}"></math>`);
  255. }
  256. }));