mathspeak_util.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import { Span } from '../audio/span.js';
  2. import * as BaseUtil from '../common/base_util.js';
  3. import * as DomUtil from '../common/dom_util.js';
  4. import * as XpathUtil from '../common/xpath_util.js';
  5. import { LOCALE } from '../l10n/locale.js';
  6. import { SemanticFont, SemanticRole, SemanticType } from '../semantic_tree/semantic_meaning.js';
  7. import { SemanticProcessor } from '../semantic_tree/semantic_processor.js';
  8. let nestingDepth = {};
  9. export function spaceoutText(node) {
  10. return Array.from(node.textContent).map(Span.stringEmpty);
  11. }
  12. function spaceoutNodes(node, correction) {
  13. const content = Array.from(node.textContent);
  14. const result = [];
  15. const processor = SemanticProcessor.getInstance();
  16. const doc = node.ownerDocument;
  17. for (let i = 0, chr; (chr = content[i]); i++) {
  18. const leaf = processor
  19. .getNodeFactory()
  20. .makeLeafNode(chr, SemanticFont.UNKNOWN);
  21. const sn = processor.identifierNode(leaf, SemanticFont.UNKNOWN, '');
  22. correction(sn);
  23. result.push(sn.xml(doc));
  24. }
  25. return result;
  26. }
  27. export function spaceoutNumber(node) {
  28. return spaceoutNodes(node, function (sn) {
  29. if (!sn.textContent.match(/\W/)) {
  30. sn.type = SemanticType.NUMBER;
  31. }
  32. });
  33. }
  34. export function spaceoutIdentifier(node) {
  35. return spaceoutNodes(node, function (sn) {
  36. sn.font = SemanticFont.UNKNOWN;
  37. sn.type = SemanticType.IDENTIFIER;
  38. });
  39. }
  40. const nestingBarriers = [
  41. SemanticType.CASES,
  42. SemanticType.CELL,
  43. SemanticType.INTEGRAL,
  44. SemanticType.LINE,
  45. SemanticType.MATRIX,
  46. SemanticType.MULTILINE,
  47. SemanticType.OVERSCORE,
  48. SemanticType.ROOT,
  49. SemanticType.ROW,
  50. SemanticType.SQRT,
  51. SemanticType.SUBSCRIPT,
  52. SemanticType.SUPERSCRIPT,
  53. SemanticType.TABLE,
  54. SemanticType.UNDERSCORE,
  55. SemanticType.VECTOR
  56. ];
  57. export function resetNestingDepth(node) {
  58. nestingDepth = {};
  59. return [node];
  60. }
  61. function getNestingDepth(type, node, tags, opt_barrierTags, opt_barrierAttrs, opt_func) {
  62. opt_barrierTags = opt_barrierTags || nestingBarriers;
  63. opt_barrierAttrs = opt_barrierAttrs || {};
  64. opt_func =
  65. opt_func ||
  66. function (_node) {
  67. return false;
  68. };
  69. const xmlText = DomUtil.serializeXml(node);
  70. if (!nestingDepth[type]) {
  71. nestingDepth[type] = {};
  72. }
  73. if (nestingDepth[type][xmlText]) {
  74. return nestingDepth[type][xmlText];
  75. }
  76. if (opt_func(node) || tags.indexOf(node.tagName) < 0) {
  77. return 0;
  78. }
  79. const depth = computeNestingDepth_(node, tags, BaseUtil.setdifference(opt_barrierTags, tags), opt_barrierAttrs, opt_func, 0);
  80. nestingDepth[type][xmlText] = depth;
  81. return depth;
  82. }
  83. function containsAttr(node, attrs) {
  84. if (!node.attributes) {
  85. return false;
  86. }
  87. const attributes = DomUtil.toArray(node.attributes);
  88. for (let i = 0, attr; (attr = attributes[i]); i++) {
  89. if (attrs[attr.nodeName] === attr.nodeValue) {
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. function computeNestingDepth_(node, tags, barriers, attrs, func, depth) {
  96. if (func(node) ||
  97. barriers.indexOf(node.tagName) > -1 ||
  98. containsAttr(node, attrs)) {
  99. return depth;
  100. }
  101. if (tags.indexOf(node.tagName) > -1) {
  102. depth++;
  103. }
  104. if (!node.childNodes || node.childNodes.length === 0) {
  105. return depth;
  106. }
  107. const children = DomUtil.toArray(node.childNodes);
  108. return Math.max.apply(null, children.map(function (subNode) {
  109. return computeNestingDepth_(subNode, tags, barriers, attrs, func, depth);
  110. }));
  111. }
  112. export function fractionNestingDepth(node) {
  113. return getNestingDepth('fraction', node, ['fraction'], nestingBarriers, {}, LOCALE.FUNCTIONS.fracNestDepth);
  114. }
  115. function nestedFraction(node, expr, opt_end) {
  116. const depth = fractionNestingDepth(node);
  117. const annotation = Array(depth).fill(expr);
  118. if (opt_end) {
  119. annotation.push(opt_end);
  120. }
  121. return annotation.join(LOCALE.MESSAGES.regexp.JOINER_FRAC);
  122. }
  123. export function openingFractionVerbose(node) {
  124. return Span.singleton(nestedFraction(node, LOCALE.MESSAGES.MS.START, LOCALE.MESSAGES.MS.FRAC_V));
  125. }
  126. export function closingFractionVerbose(node) {
  127. return Span.singleton(nestedFraction(node, LOCALE.MESSAGES.MS.END, LOCALE.MESSAGES.MS.FRAC_V), { kind: 'LAST' });
  128. }
  129. export function overFractionVerbose(node) {
  130. return Span.singleton(nestedFraction(node, LOCALE.MESSAGES.MS.FRAC_OVER), {});
  131. }
  132. export function openingFractionBrief(node) {
  133. return Span.singleton(nestedFraction(node, LOCALE.MESSAGES.MS.START, LOCALE.MESSAGES.MS.FRAC_B));
  134. }
  135. export function closingFractionBrief(node) {
  136. return Span.singleton(nestedFraction(node, LOCALE.MESSAGES.MS.END, LOCALE.MESSAGES.MS.FRAC_B), { kind: 'LAST' });
  137. }
  138. export function openingFractionSbrief(node) {
  139. const depth = fractionNestingDepth(node);
  140. return Span.singleton(depth === 1
  141. ? LOCALE.MESSAGES.MS.FRAC_S
  142. : LOCALE.FUNCTIONS.combineNestedFraction(LOCALE.MESSAGES.MS.NEST_FRAC, LOCALE.FUNCTIONS.radicalNestDepth(depth - 1), LOCALE.MESSAGES.MS.FRAC_S));
  143. }
  144. export function closingFractionSbrief(node) {
  145. const depth = fractionNestingDepth(node);
  146. return Span.singleton(depth === 1
  147. ? LOCALE.MESSAGES.MS.ENDFRAC
  148. : LOCALE.FUNCTIONS.combineNestedFraction(LOCALE.MESSAGES.MS.NEST_FRAC, LOCALE.FUNCTIONS.radicalNestDepth(depth - 1), LOCALE.MESSAGES.MS.ENDFRAC), { kind: 'LAST' });
  149. }
  150. export function overFractionSbrief(node) {
  151. const depth = fractionNestingDepth(node);
  152. return Span.singleton(depth === 1
  153. ? LOCALE.MESSAGES.MS.FRAC_OVER
  154. : LOCALE.FUNCTIONS.combineNestedFraction(LOCALE.MESSAGES.MS.NEST_FRAC, LOCALE.FUNCTIONS.radicalNestDepth(depth - 1), LOCALE.MESSAGES.MS.FRAC_OVER));
  155. }
  156. export function isSmallVulgarFraction(node) {
  157. return LOCALE.FUNCTIONS.fracNestDepth(node) ? [node] : [];
  158. }
  159. export function nestedSubSuper(node, init, replace) {
  160. while (node.parentNode) {
  161. const children = node.parentNode;
  162. const parent = children.parentNode;
  163. if (!parent) {
  164. break;
  165. }
  166. const nodeRole = node.getAttribute && node.getAttribute('role');
  167. if ((parent.tagName === SemanticType.SUBSCRIPT &&
  168. node === children.childNodes[1]) ||
  169. (parent.tagName === SemanticType.TENSOR &&
  170. nodeRole &&
  171. (nodeRole === SemanticRole.LEFTSUB ||
  172. nodeRole === SemanticRole.RIGHTSUB))) {
  173. init = replace.sub + LOCALE.MESSAGES.regexp.JOINER_SUBSUPER + init;
  174. }
  175. if ((parent.tagName === SemanticType.SUPERSCRIPT &&
  176. node === children.childNodes[1]) ||
  177. (parent.tagName === SemanticType.TENSOR &&
  178. nodeRole &&
  179. (nodeRole === SemanticRole.LEFTSUPER ||
  180. nodeRole === SemanticRole.RIGHTSUPER))) {
  181. init = replace.sup + LOCALE.MESSAGES.regexp.JOINER_SUBSUPER + init;
  182. }
  183. node = parent;
  184. }
  185. return init.trim();
  186. }
  187. export function subscriptVerbose(node) {
  188. return Span.singleton(nestedSubSuper(node, LOCALE.MESSAGES.MS.SUBSCRIPT, {
  189. sup: LOCALE.MESSAGES.MS.SUPER,
  190. sub: LOCALE.MESSAGES.MS.SUB
  191. }));
  192. }
  193. export function subscriptBrief(node) {
  194. return Span.singleton(nestedSubSuper(node, LOCALE.MESSAGES.MS.SUB, {
  195. sup: LOCALE.MESSAGES.MS.SUP,
  196. sub: LOCALE.MESSAGES.MS.SUB
  197. }));
  198. }
  199. export function superscriptVerbose(node) {
  200. return Span.singleton(nestedSubSuper(node, LOCALE.MESSAGES.MS.SUPERSCRIPT, {
  201. sup: LOCALE.MESSAGES.MS.SUPER,
  202. sub: LOCALE.MESSAGES.MS.SUB
  203. }));
  204. }
  205. export function superscriptBrief(node) {
  206. return Span.singleton(nestedSubSuper(node, LOCALE.MESSAGES.MS.SUP, {
  207. sup: LOCALE.MESSAGES.MS.SUP,
  208. sub: LOCALE.MESSAGES.MS.SUB
  209. }));
  210. }
  211. export function baselineVerbose(node) {
  212. const baseline = nestedSubSuper(node, '', {
  213. sup: LOCALE.MESSAGES.MS.SUPER,
  214. sub: LOCALE.MESSAGES.MS.SUB
  215. });
  216. return Span.singleton(!baseline
  217. ? LOCALE.MESSAGES.MS.BASELINE
  218. : baseline
  219. .replace(new RegExp(LOCALE.MESSAGES.MS.SUB + '$'), LOCALE.MESSAGES.MS.SUBSCRIPT)
  220. .replace(new RegExp(LOCALE.MESSAGES.MS.SUPER + '$'), LOCALE.MESSAGES.MS.SUPERSCRIPT));
  221. }
  222. export function baselineBrief(node) {
  223. const baseline = nestedSubSuper(node, '', {
  224. sup: LOCALE.MESSAGES.MS.SUP,
  225. sub: LOCALE.MESSAGES.MS.SUB
  226. });
  227. return Span.singleton(baseline || LOCALE.MESSAGES.MS.BASE);
  228. }
  229. export function radicalNestingDepth(node) {
  230. return getNestingDepth('radical', node, ['sqrt', 'root'], nestingBarriers, {});
  231. }
  232. function nestedRadical(node, prefix, postfix) {
  233. const depth = radicalNestingDepth(node);
  234. const index = getRootIndex(node);
  235. postfix = index ? LOCALE.FUNCTIONS.combineRootIndex(postfix, index) : postfix;
  236. return depth === 1
  237. ? postfix
  238. : LOCALE.FUNCTIONS.combineNestedRadical(prefix, LOCALE.FUNCTIONS.radicalNestDepth(depth - 1), postfix);
  239. }
  240. function getRootIndex(node) {
  241. const content = node.tagName === 'sqrt'
  242. ? '2'
  243. :
  244. XpathUtil.evalXPath('children/*[1]', node)[0].textContent.trim();
  245. return LOCALE.MESSAGES.MSroots[content] || '';
  246. }
  247. export function openingRadicalVerbose(node) {
  248. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NESTED, LOCALE.MESSAGES.MS.STARTROOT));
  249. }
  250. export function closingRadicalVerbose(node) {
  251. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NESTED, LOCALE.MESSAGES.MS.ENDROOT));
  252. }
  253. export function indexRadicalVerbose(node) {
  254. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NESTED, LOCALE.MESSAGES.MS.ROOTINDEX));
  255. }
  256. export function openingRadicalBrief(node) {
  257. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NEST_ROOT, LOCALE.MESSAGES.MS.STARTROOT));
  258. }
  259. export function closingRadicalBrief(node) {
  260. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NEST_ROOT, LOCALE.MESSAGES.MS.ENDROOT));
  261. }
  262. export function indexRadicalBrief(node) {
  263. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NEST_ROOT, LOCALE.MESSAGES.MS.ROOTINDEX));
  264. }
  265. export function openingRadicalSbrief(node) {
  266. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NEST_ROOT, LOCALE.MESSAGES.MS.ROOT));
  267. }
  268. export function indexRadicalSbrief(node) {
  269. return Span.singleton(nestedRadical(node, LOCALE.MESSAGES.MS.NEST_ROOT, LOCALE.MESSAGES.MS.INDEX));
  270. }
  271. function underscoreNestingDepth(node) {
  272. return getNestingDepth('underscore', node, ['underscore'], nestingBarriers, {}, function (node) {
  273. return (node.tagName &&
  274. node.tagName === SemanticType.UNDERSCORE &&
  275. node.childNodes[0].childNodes[1].getAttribute('role') ===
  276. SemanticRole.UNDERACCENT);
  277. });
  278. }
  279. export function nestedUnderscript(node) {
  280. const depth = underscoreNestingDepth(node);
  281. return Span.singleton(Array(depth).join(LOCALE.MESSAGES.MS.UNDER) + LOCALE.MESSAGES.MS.UNDERSCRIPT);
  282. }
  283. function overscoreNestingDepth(node) {
  284. return getNestingDepth('overscore', node, ['overscore'], nestingBarriers, {}, function (node) {
  285. return (node.tagName &&
  286. node.tagName === SemanticType.OVERSCORE &&
  287. node.childNodes[0].childNodes[1].getAttribute('role') ===
  288. SemanticRole.OVERACCENT);
  289. });
  290. }
  291. export function endscripts(_node) {
  292. return Span.singleton(LOCALE.MESSAGES.MS.ENDSCRIPTS);
  293. }
  294. export function nestedOverscript(node) {
  295. const depth = overscoreNestingDepth(node);
  296. return Span.singleton(Array(depth).join(LOCALE.MESSAGES.MS.OVER) + LOCALE.MESSAGES.MS.OVERSCRIPT);
  297. }
  298. export function determinantIsSimple(node) {
  299. if (node.tagName !== SemanticType.MATRIX ||
  300. node.getAttribute('role') !== SemanticRole.DETERMINANT) {
  301. return [];
  302. }
  303. const cells = XpathUtil.evalXPath('children/row/children/cell/children/*', node);
  304. for (let i = 0, cell; (cell = cells[i]); i++) {
  305. if (cell.tagName === SemanticType.NUMBER) {
  306. continue;
  307. }
  308. if (cell.tagName === SemanticType.IDENTIFIER) {
  309. const role = cell.getAttribute('role');
  310. if (role === SemanticRole.LATINLETTER ||
  311. role === SemanticRole.GREEKLETTER ||
  312. role === SemanticRole.OTHERLETTER) {
  313. continue;
  314. }
  315. }
  316. return [];
  317. }
  318. return [node];
  319. }
  320. export function generateBaselineConstraint() {
  321. const ignoreElems = ['subscript', 'superscript', 'tensor'];
  322. const mainElems = ['relseq', 'multrel'];
  323. const breakElems = ['fraction', 'punctuation', 'fenced', 'sqrt', 'root'];
  324. const ancestrify = (elemList) => elemList.map((elem) => 'ancestor::' + elem);
  325. const notify = (elem) => 'not(' + elem + ')';
  326. const prefix = 'ancestor::*/following-sibling::*';
  327. const middle = notify(ancestrify(ignoreElems).join(' or '));
  328. const mainList = ancestrify(mainElems);
  329. const breakList = ancestrify(breakElems);
  330. let breakCstrs = [];
  331. for (let i = 0, brk; (brk = breakList[i]); i++) {
  332. breakCstrs = breakCstrs.concat(mainList.map(function (elem) {
  333. return brk + '/' + elem;
  334. }));
  335. }
  336. const postfix = notify(breakCstrs.join(' | '));
  337. return [[prefix, middle, postfix].join(' and ')];
  338. }
  339. export function removeParens(node) {
  340. if (!node.childNodes.length ||
  341. !node.childNodes[0].childNodes.length ||
  342. !node.childNodes[0].childNodes[0].childNodes.length) {
  343. return Span.singleton('');
  344. }
  345. const content = node.childNodes[0].childNodes[0].childNodes[0].textContent;
  346. return Span.singleton(content.match(/^\(.+\)$/) ? content.slice(1, -1) : content);
  347. }
  348. const componentString = new Map([
  349. [3, 'CSFleftsuperscript'],
  350. [4, 'CSFleftsubscript'],
  351. [2, 'CSFbaseline'],
  352. [1, 'CSFrightsubscript'],
  353. [0, 'CSFrightsuperscript']
  354. ]);
  355. const childNumber = new Map([
  356. [4, 2],
  357. [3, 3],
  358. [2, 1],
  359. [1, 4],
  360. [0, 5]
  361. ]);
  362. function generateTensorRuleStrings_(constellation) {
  363. const constraints = [];
  364. let verbString = '';
  365. let briefString = '';
  366. let constel = parseInt(constellation, 2);
  367. for (let i = 0; i < 5; i++) {
  368. const childString = 'children/*[' + childNumber.get(i) + ']';
  369. if (constel & 1) {
  370. const compString = componentString.get(i % 5);
  371. verbString =
  372. '[t] ' + compString + 'Verbose; [n] ' + childString + ';' + verbString;
  373. briefString =
  374. '[t] ' + compString + 'Brief; [n] ' + childString + ';' + briefString;
  375. }
  376. else {
  377. constraints.unshift('name(' + childString + ')="empty"');
  378. }
  379. constel >>= 1;
  380. }
  381. return [constraints, verbString, briefString];
  382. }
  383. export function generateTensorRules(store, brief = true) {
  384. const constellations = [
  385. '11111',
  386. '11110',
  387. '11101',
  388. '11100',
  389. '10111',
  390. '10110',
  391. '10101',
  392. '10100',
  393. '01111',
  394. '01110',
  395. '01101',
  396. '01100'
  397. ];
  398. for (const constel of constellations) {
  399. let name = 'tensor' + constel;
  400. let [components, verbStr, briefStr] = generateTensorRuleStrings_(constel);
  401. store.defineRule(name, 'default', verbStr, 'self::tensor', ...components);
  402. if (brief) {
  403. store.defineRule(name, 'brief', briefStr, 'self::tensor', ...components);
  404. store.defineRule(name, 'sbrief', briefStr, 'self::tensor', ...components);
  405. }
  406. if (!(parseInt(constel, 2) & 3)) {
  407. continue;
  408. }
  409. const baselineStr = componentString.get(2);
  410. verbStr += '; [t]' + baselineStr + 'Verbose';
  411. briefStr += '; [t]' + baselineStr + 'Brief';
  412. name = name + '-baseline';
  413. const cstr = '((.//*[not(*)])[last()]/@id)!=(((.//ancestor::fraction|' +
  414. 'ancestor::root|ancestor::sqrt|ancestor::cell|ancestor::line|' +
  415. 'ancestor::stree)[1]//*[not(*)])[last()]/@id)';
  416. store.defineRule(name, 'default', verbStr, 'self::tensor', cstr, ...components);
  417. if (brief) {
  418. store.defineRule(name, 'brief', briefStr, 'self::tensor', cstr, ...components);
  419. store.defineRule(name, 'sbrief', briefStr, 'self::tensor', cstr, ...components);
  420. }
  421. }
  422. }
  423. export function smallRoot(node) {
  424. let max = Object.keys(LOCALE.MESSAGES.MSroots).length;
  425. if (!max) {
  426. return [];
  427. }
  428. else {
  429. max++;
  430. }
  431. if (!node.childNodes ||
  432. node.childNodes.length === 0 ||
  433. !node.childNodes[0].childNodes) {
  434. return [];
  435. }
  436. const index = node.childNodes[0].childNodes[0].textContent;
  437. if (!/^\d+$/.test(index)) {
  438. return [];
  439. }
  440. const num = parseInt(index, 10);
  441. return num > 1 && num <= max ? [node] : [];
  442. }