nemeth_util.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import { AuditoryDescription } from '../audio/auditory_description.js';
  2. import { Span } from '../audio/span.js';
  3. import * as DomUtil from '../common/dom_util.js';
  4. import * as XpathUtil from '../common/xpath_util.js';
  5. import { Grammar, correctFont } from '../rule_engine/grammar.js';
  6. import { Engine } from '../common/engine.js';
  7. import { register, activate } from '../semantic_tree/semantic_annotations.js';
  8. import { SemanticVisitor } from '../semantic_tree/semantic_annotator.js';
  9. import { SemanticRole, SemanticType } from '../semantic_tree/semantic_meaning.js';
  10. import { LOCALE } from '../l10n/locale.js';
  11. import * as MathspeakUtil from './mathspeak_util.js';
  12. import { contentIterator as suCI } from '../rule_engine/store_util.js';
  13. export function openingFraction(node) {
  14. const depth = MathspeakUtil.fractionNestingDepth(node);
  15. return Span.singleton(new Array(depth).join(LOCALE.MESSAGES.MS.FRACTION_REPEAT) +
  16. LOCALE.MESSAGES.MS.FRACTION_START);
  17. }
  18. export function closingFraction(node) {
  19. const depth = MathspeakUtil.fractionNestingDepth(node);
  20. return Span.singleton(new Array(depth).join(LOCALE.MESSAGES.MS.FRACTION_REPEAT) +
  21. LOCALE.MESSAGES.MS.FRACTION_END);
  22. }
  23. export function overFraction(node) {
  24. const depth = MathspeakUtil.fractionNestingDepth(node);
  25. return Span.singleton(new Array(depth).join(LOCALE.MESSAGES.MS.FRACTION_REPEAT) +
  26. LOCALE.MESSAGES.MS.FRACTION_OVER);
  27. }
  28. export function overBevelledFraction(node) {
  29. const depth = MathspeakUtil.fractionNestingDepth(node);
  30. return Span.singleton(new Array(depth).join(LOCALE.MESSAGES.MS.FRACTION_REPEAT) +
  31. '⠸' +
  32. LOCALE.MESSAGES.MS.FRACTION_OVER);
  33. }
  34. export function hyperFractionBoundary(node) {
  35. return LOCALE.MESSAGES.regexp.HYPER ===
  36. MathspeakUtil.fractionNestingDepth(node).toString()
  37. ? [node]
  38. : [];
  39. }
  40. function nestedRadical(node, postfix) {
  41. const depth = radicalNestingDepth(node);
  42. return Span.singleton(depth === 1
  43. ? postfix
  44. : new Array(depth).join(LOCALE.MESSAGES.MS.NESTED) + postfix);
  45. }
  46. function radicalNestingDepth(node, opt_depth) {
  47. const depth = opt_depth || 0;
  48. if (!node.parentNode) {
  49. return depth;
  50. }
  51. return radicalNestingDepth(node.parentNode, node.tagName === 'root' || node.tagName === 'sqrt' ? depth + 1 : depth);
  52. }
  53. export function openingRadical(node) {
  54. return nestedRadical(node, LOCALE.MESSAGES.MS.STARTROOT);
  55. }
  56. export function closingRadical(node) {
  57. return nestedRadical(node, LOCALE.MESSAGES.MS.ENDROOT);
  58. }
  59. export function indexRadical(node) {
  60. return nestedRadical(node, LOCALE.MESSAGES.MS.ROOTINDEX);
  61. }
  62. function enlargeFence(text) {
  63. const start = '⠠';
  64. if (text.length === 1) {
  65. return start + text;
  66. }
  67. const neut = '⠳';
  68. const split = text.split('');
  69. if (split.every(function (x) {
  70. return x === neut;
  71. })) {
  72. return start + split.join(start);
  73. }
  74. return text.slice(0, -1) + start + text.slice(-1);
  75. }
  76. Grammar.getInstance().setCorrection('enlargeFence', enlargeFence);
  77. const NUMBER_PROPAGATORS = [
  78. SemanticType.MULTIREL,
  79. SemanticType.RELSEQ,
  80. SemanticType.APPL,
  81. SemanticType.ROW,
  82. SemanticType.LINE
  83. ];
  84. const NUMBER_INHIBITORS = [
  85. SemanticType.SUBSCRIPT,
  86. SemanticType.SUPERSCRIPT,
  87. SemanticType.OVERSCORE,
  88. SemanticType.UNDERSCORE
  89. ];
  90. function checkParent(node, info) {
  91. const parent = node.parent;
  92. if (!parent) {
  93. return false;
  94. }
  95. const type = parent.type;
  96. if (NUMBER_PROPAGATORS.indexOf(type) !== -1 ||
  97. (type === SemanticType.PREFIXOP &&
  98. parent.role === SemanticRole.NEGATIVE &&
  99. !info.script &&
  100. !info.enclosed) ||
  101. (type === SemanticType.PREFIXOP &&
  102. parent.role === SemanticRole.GEOMETRY)) {
  103. return true;
  104. }
  105. if (type === SemanticType.PUNCTUATED) {
  106. if (!info.enclosed || parent.role === SemanticRole.TEXT) {
  107. return true;
  108. }
  109. }
  110. return false;
  111. }
  112. function propagateNumber(node, info) {
  113. if (!node.childNodes.length) {
  114. if (checkParent(node, info)) {
  115. info.number = true;
  116. info.script = false;
  117. info.enclosed = false;
  118. }
  119. return [
  120. info['number'] ? 'number' : '',
  121. { number: false, enclosed: info.enclosed, script: info.script }
  122. ];
  123. }
  124. if (NUMBER_INHIBITORS.indexOf(node.type) !== -1) {
  125. info.script = true;
  126. }
  127. if (node.type === SemanticType.FENCED) {
  128. info.number = false;
  129. info.enclosed = true;
  130. return ['', info];
  131. }
  132. if (node.type === SemanticType.PREFIXOP &&
  133. node.role !== SemanticRole.GEOMETRY &&
  134. node.role !== SemanticRole.NEGATIVE) {
  135. info.number = false;
  136. return ['', info];
  137. }
  138. if (checkParent(node, info)) {
  139. info.number = true;
  140. info.enclosed = false;
  141. }
  142. return ['', info];
  143. }
  144. register(new SemanticVisitor('nemeth', 'number', propagateNumber, { number: true }));
  145. function annotateDepth(node) {
  146. if (!node.parent) {
  147. return [1];
  148. }
  149. const depth = parseInt(node.parent.annotation['depth'][0]);
  150. return [depth + 1];
  151. }
  152. register(new SemanticVisitor('depth', 'depth', annotateDepth));
  153. activate('depth', 'depth');
  154. export function relationIterator(nodes, context) {
  155. var _a;
  156. const childNodes = nodes.slice(0);
  157. let first = true;
  158. const parentNode = nodes[0].parentNode.parentNode;
  159. const match = (_a = parentNode.getAttribute('annotation')) === null || _a === void 0 ? void 0 : _a.match(/depth:(\d+)/);
  160. const depth = match ? match[1] : '';
  161. let contentNodes;
  162. if (nodes.length > 0) {
  163. contentNodes = XpathUtil.evalXPath('./content/*', parentNode);
  164. }
  165. else {
  166. contentNodes = [];
  167. }
  168. return function () {
  169. const content = contentNodes.shift();
  170. const leftChild = childNodes.shift();
  171. const rightChild = childNodes[0];
  172. const contextDescr = context
  173. ? [AuditoryDescription.create({ text: context }, { translate: true })]
  174. : [];
  175. if (!content) {
  176. return contextDescr;
  177. }
  178. const base = leftChild
  179. ? MathspeakUtil.nestedSubSuper(leftChild, '', {
  180. sup: LOCALE.MESSAGES.MS.SUPER,
  181. sub: LOCALE.MESSAGES.MS.SUB
  182. })
  183. : '';
  184. const left = (leftChild && DomUtil.tagName(leftChild) !== 'EMPTY') ||
  185. (first && parentNode && parentNode.previousSibling)
  186. ? [
  187. AuditoryDescription.create({ text: LOCALE.MESSAGES.regexp.SPACE + base }, {})
  188. ]
  189. : [];
  190. const right = (rightChild && DomUtil.tagName(rightChild) !== 'EMPTY') ||
  191. (!contentNodes.length && parentNode && parentNode.nextSibling)
  192. ? [
  193. AuditoryDescription.create({ text: LOCALE.MESSAGES.regexp.SPACE }, {})
  194. ]
  195. : [];
  196. const descrs = Engine.evaluateNode(content);
  197. descrs.unshift(new AuditoryDescription({ text: '', layout: `beginrel${depth}` }));
  198. descrs.push(new AuditoryDescription({ text: '', layout: `endrel${depth}` }));
  199. first = false;
  200. return contextDescr.concat(left, descrs, right);
  201. };
  202. }
  203. export function implicitIterator(nodes, context) {
  204. const childNodes = nodes.slice(0);
  205. let contentNodes;
  206. if (nodes.length > 0) {
  207. contentNodes = XpathUtil.evalXPath('../../content/*', nodes[0]);
  208. }
  209. else {
  210. contentNodes = [];
  211. }
  212. return function () {
  213. const leftChild = childNodes.shift();
  214. const rightChild = childNodes[0];
  215. const content = contentNodes.shift();
  216. const contextDescr = context
  217. ? [AuditoryDescription.create({ text: context }, { translate: true })]
  218. : [];
  219. if (!content) {
  220. return contextDescr;
  221. }
  222. const left = leftChild && DomUtil.tagName(leftChild) === 'NUMBER';
  223. const right = rightChild && DomUtil.tagName(rightChild) === 'NUMBER';
  224. return contextDescr.concat(left && right && content.getAttribute('role') === SemanticRole.SPACE
  225. ? [
  226. AuditoryDescription.create({ text: LOCALE.MESSAGES.regexp.SPACE }, {})
  227. ]
  228. : []);
  229. };
  230. }
  231. function ignoreEnglish(text) {
  232. return correctFont(text, LOCALE.ALPHABETS.languagePrefix.english);
  233. }
  234. Grammar.getInstance().setCorrection('ignoreEnglish', ignoreEnglish);
  235. export function contentIterator(nodes, context) {
  236. var _a;
  237. const func = suCI(nodes, context);
  238. const parentNode = nodes[0].parentNode.parentNode;
  239. const match = (_a = parentNode.getAttribute('annotation')) === null || _a === void 0 ? void 0 : _a.match(/depth:(\d+)/);
  240. const depth = match ? match[1] : '';
  241. return function () {
  242. const descrs = func();
  243. descrs.unshift(new AuditoryDescription({ text: '', layout: `beginrel${depth}` }));
  244. descrs.push(new AuditoryDescription({ text: '', layout: `endrel${depth}` }));
  245. return descrs;
  246. };
  247. }
  248. function literal(text) {
  249. const evalStr = (e) => Engine.getInstance().evaluator(e, Engine.getInstance().dynamicCstr);
  250. return Array.from(text).map(evalStr).join('');
  251. }
  252. Grammar.getInstance().setCorrection('literal', literal);