clearspeak_util.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { Span } from '../audio/span.js';
  2. import * as DomUtil from '../common/dom_util.js';
  3. import { Engine } from '../common/engine.js';
  4. import * as XpathUtil from '../common/xpath_util.js';
  5. import { LOCALE } from '../l10n/locale.js';
  6. import { vulgarFractionSmall } from '../l10n/transformers.js';
  7. import { Grammar } from '../rule_engine/grammar.js';
  8. import * as StoreUtil from '../rule_engine/store_util.js';
  9. import { register } from '../semantic_tree/semantic_annotations.js';
  10. import { SemanticAnnotator } from '../semantic_tree/semantic_annotator.js';
  11. import { isMatchingFence } from '../semantic_tree/semantic_attr.js';
  12. import { SemanticRole, SemanticType } from '../semantic_tree/semantic_meaning.js';
  13. export function nodeCounter(nodes, context) {
  14. const split = context.split('-');
  15. const func = StoreUtil.nodeCounter(nodes, split[0] || '');
  16. const sep = split[1] || '';
  17. const init = split[2] || '';
  18. let first = true;
  19. return function () {
  20. const result = func();
  21. if (first) {
  22. first = false;
  23. return init + result + sep;
  24. }
  25. else {
  26. return result + sep;
  27. }
  28. };
  29. }
  30. function isSimpleExpression(node) {
  31. return (isSimpleNumber_(node) ||
  32. isSimpleLetters_(node) ||
  33. isSimpleDegree_(node) ||
  34. isSimpleNegative_(node) ||
  35. isSimpleFunction_(node));
  36. }
  37. function isSimpleFunction_(node) {
  38. return (node.type === SemanticType.APPL &&
  39. (node.childNodes[0].role === SemanticRole.PREFIXFUNC ||
  40. node.childNodes[0].role === SemanticRole.SIMPLEFUNC) &&
  41. (isSimple_(node.childNodes[1]) ||
  42. (node.childNodes[1].type === SemanticType.FENCED &&
  43. isSimple_(node.childNodes[1].childNodes[0]))));
  44. }
  45. function isSimpleNegative_(node) {
  46. return (node.type === SemanticType.PREFIXOP &&
  47. node.role === SemanticRole.NEGATIVE &&
  48. isSimple_(node.childNodes[0]) &&
  49. node.childNodes[0].type !== SemanticType.PREFIXOP &&
  50. node.childNodes[0].type !== SemanticType.APPL &&
  51. node.childNodes[0].type !== SemanticType.PUNCTUATED);
  52. }
  53. function isSimpleDegree_(node) {
  54. return (node.type === SemanticType.PUNCTUATED &&
  55. node.role === SemanticRole.ENDPUNCT &&
  56. node.childNodes.length === 2 &&
  57. node.childNodes[1].role === SemanticRole.DEGREE &&
  58. (isLetter_(node.childNodes[0]) ||
  59. isNumber_(node.childNodes[0]) ||
  60. (node.childNodes[0].type === SemanticType.PREFIXOP &&
  61. node.childNodes[0].role === SemanticRole.NEGATIVE &&
  62. (isLetter_(node.childNodes[0].childNodes[0]) ||
  63. isNumber_(node.childNodes[0].childNodes[0])))));
  64. }
  65. function isSimpleLetters_(node) {
  66. return (isLetter_(node) ||
  67. (node.type === SemanticType.INFIXOP &&
  68. node.role === SemanticRole.IMPLICIT &&
  69. ((node.childNodes.length === 2 &&
  70. (isLetter_(node.childNodes[0]) ||
  71. isSimpleNumber_(node.childNodes[0])) &&
  72. isLetter_(node.childNodes[1])) ||
  73. (node.childNodes.length === 3 &&
  74. isSimpleNumber_(node.childNodes[0]) &&
  75. isLetter_(node.childNodes[1]) &&
  76. isLetter_(node.childNodes[2])))));
  77. }
  78. function isSimple_(node) {
  79. return node.hasAnnotation('clearspeak', 'simple');
  80. }
  81. function isLetter_(node) {
  82. return (node.type === SemanticType.IDENTIFIER &&
  83. (node.role === SemanticRole.LATINLETTER ||
  84. node.role === SemanticRole.GREEKLETTER ||
  85. node.role === SemanticRole.OTHERLETTER ||
  86. node.role === SemanticRole.SIMPLEFUNC));
  87. }
  88. function isNumber_(node) {
  89. return (node.type === SemanticType.NUMBER &&
  90. (node.role === SemanticRole.INTEGER || node.role === SemanticRole.FLOAT));
  91. }
  92. function isSimpleNumber_(node) {
  93. return isNumber_(node) || isSimpleFraction_(node);
  94. }
  95. function isSimpleFraction_(node) {
  96. if (hasPreference('Fraction_Over') || hasPreference('Fraction_FracOver')) {
  97. return false;
  98. }
  99. if (node.type !== SemanticType.FRACTION ||
  100. node.role !== SemanticRole.VULGAR) {
  101. return false;
  102. }
  103. if (hasPreference('Fraction_Ordinal')) {
  104. return true;
  105. }
  106. const enumerator = parseInt(node.childNodes[0].textContent, 10);
  107. const denominator = parseInt(node.childNodes[1].textContent, 10);
  108. return (enumerator > 0 && enumerator < 20 && denominator > 0 && denominator < 11);
  109. }
  110. function hasPreference(pref) {
  111. return Engine.getInstance().style === pref;
  112. }
  113. register(new SemanticAnnotator('clearspeak', 'simple', function (node) {
  114. return isSimpleExpression(node) ? 'simple' : '';
  115. }));
  116. function simpleNode(node) {
  117. if (!node.hasAttribute('annotation')) {
  118. return false;
  119. }
  120. const annotation = node.getAttribute('annotation');
  121. return !!/clearspeak:simple$|clearspeak:simple;/.exec(annotation);
  122. }
  123. function simpleCell_(node) {
  124. if (simpleNode(node)) {
  125. return true;
  126. }
  127. if (node.tagName !== SemanticType.SUBSCRIPT) {
  128. return false;
  129. }
  130. const children = node.childNodes[0].childNodes;
  131. const index = children[1];
  132. return (children[0].tagName === SemanticType.IDENTIFIER &&
  133. (isInteger_(index) ||
  134. (index.tagName === SemanticType.INFIXOP &&
  135. index.hasAttribute('role') &&
  136. index.getAttribute('role') === SemanticRole.IMPLICIT &&
  137. allIndices_(index))));
  138. }
  139. function isInteger_(node) {
  140. return (node.tagName === SemanticType.NUMBER &&
  141. node.hasAttribute('role') &&
  142. node.getAttribute('role') === SemanticRole.INTEGER);
  143. }
  144. function allIndices_(node) {
  145. const nodes = XpathUtil.evalXPath('children/*', node);
  146. return nodes.every((x) => isInteger_(x) || x.tagName === SemanticType.IDENTIFIER);
  147. }
  148. export function allCellsSimple(node) {
  149. const xpath = node.tagName === SemanticType.MATRIX
  150. ? 'children/row/children/cell/children/*'
  151. : 'children/line/children/*';
  152. const nodes = XpathUtil.evalXPath(xpath, node);
  153. const result = nodes.every(simpleCell_);
  154. return result ? [node] : [];
  155. }
  156. export function isSmallVulgarFraction(node) {
  157. return vulgarFractionSmall(node, 20, 11) ? [node] : [];
  158. }
  159. function isUnitExpression(node) {
  160. return ((node.type === SemanticType.TEXT && node.role !== SemanticRole.LABEL) ||
  161. (node.type === SemanticType.PUNCTUATED &&
  162. node.role === SemanticRole.TEXT &&
  163. isNumber_(node.childNodes[0]) &&
  164. allTextLastContent_(node.childNodes.slice(1))) ||
  165. (node.type === SemanticType.IDENTIFIER &&
  166. node.role === SemanticRole.UNIT) ||
  167. (node.type === SemanticType.INFIXOP &&
  168. (node.role === SemanticRole.IMPLICIT || node.role === SemanticRole.UNIT)));
  169. }
  170. function allTextLastContent_(nodes) {
  171. for (let i = 0; i < nodes.length - 1; i++) {
  172. if (!(nodes[i].type === SemanticType.TEXT && nodes[i].textContent === '')) {
  173. return false;
  174. }
  175. }
  176. return nodes[nodes.length - 1].type === SemanticType.TEXT;
  177. }
  178. register(new SemanticAnnotator('clearspeak', 'unit', function (node) {
  179. return isUnitExpression(node) ? 'unit' : '';
  180. }));
  181. export function ordinalExponent(node) {
  182. const num = parseInt(node.textContent, 10);
  183. return [
  184. Span.stringEmpty(isNaN(num)
  185. ? node.textContent
  186. : num > 10
  187. ? LOCALE.NUMBERS.numericOrdinal(num)
  188. : LOCALE.NUMBERS.wordOrdinal(num))
  189. ];
  190. }
  191. let NESTING_DEPTH = null;
  192. export function nestingDepth(node) {
  193. let count = 0;
  194. const fence = node.textContent;
  195. const index = node.getAttribute('role') === 'open' ? 0 : 1;
  196. let parent = node.parentNode;
  197. while (parent) {
  198. if (parent.tagName === SemanticType.FENCED &&
  199. parent.childNodes[0].childNodes[index].textContent === fence) {
  200. count++;
  201. }
  202. parent = parent.parentNode;
  203. }
  204. NESTING_DEPTH = count > 1 ? LOCALE.NUMBERS.wordOrdinal(count) : '';
  205. return [Span.stringEmpty(NESTING_DEPTH)];
  206. }
  207. export function matchingFences(node) {
  208. const sibling = node.previousSibling;
  209. let left, right;
  210. if (sibling) {
  211. left = sibling;
  212. right = node;
  213. }
  214. else {
  215. left = node;
  216. right = node.nextSibling;
  217. }
  218. if (!right) {
  219. return [];
  220. }
  221. return isMatchingFence(left.textContent, right.textContent) ? [node] : [];
  222. }
  223. function insertNesting(text, correction) {
  224. if (!correction || !text) {
  225. return text;
  226. }
  227. const start = text.match(/^(open|close) /);
  228. if (!start) {
  229. return correction + ' ' + text;
  230. }
  231. return start[0] + correction + ' ' + text.substring(start[0].length);
  232. }
  233. Grammar.getInstance().setCorrection('insertNesting', insertNesting);
  234. export function fencedArguments(node) {
  235. const content = DomUtil.toArray(node.parentNode.childNodes);
  236. const children = XpathUtil.evalXPath('../../children/*', node);
  237. const index = content.indexOf(node);
  238. return fencedFactor_(children[index]) || fencedFactor_(children[index + 1])
  239. ? [node]
  240. : [];
  241. }
  242. export function simpleArguments(node) {
  243. const content = DomUtil.toArray(node.parentNode.childNodes);
  244. const children = XpathUtil.evalXPath('../../children/*', node);
  245. const index = content.indexOf(node);
  246. return simpleFactor_(children[index]) &&
  247. children[index + 1] &&
  248. (simpleFactor_(children[index + 1]) ||
  249. children[index + 1].tagName === SemanticType.ROOT ||
  250. children[index + 1].tagName === SemanticType.SQRT ||
  251. (children[index + 1].tagName === SemanticType.SUPERSCRIPT &&
  252. children[index + 1].childNodes[0].childNodes[0] &&
  253. (children[index + 1].childNodes[0].childNodes[0]
  254. .tagName === SemanticType.NUMBER ||
  255. children[index + 1].childNodes[0].childNodes[0]
  256. .tagName === SemanticType.IDENTIFIER) &&
  257. (children[index + 1].childNodes[0].childNodes[1].textContent === '2' ||
  258. children[index + 1].childNodes[0].childNodes[1].textContent === '3')))
  259. ? [node]
  260. : [];
  261. }
  262. function simpleFactor_(node) {
  263. return (!!node &&
  264. (node.tagName === SemanticType.NUMBER ||
  265. node.tagName === SemanticType.IDENTIFIER ||
  266. node.tagName === SemanticType.FUNCTION ||
  267. node.tagName === SemanticType.APPL ||
  268. node.tagName === SemanticType.FRACTION));
  269. }
  270. function fencedFactor_(node) {
  271. return (node &&
  272. (node.tagName === SemanticType.FENCED ||
  273. (node.hasAttribute('role') &&
  274. node.getAttribute('role') === SemanticRole.LEFTRIGHT) ||
  275. layoutFactor_(node)));
  276. }
  277. function layoutFactor_(node) {
  278. return (!!node &&
  279. (node.tagName === SemanticType.MATRIX ||
  280. node.tagName === SemanticType.VECTOR));
  281. }
  282. export function wordOrdinal(node) {
  283. return [
  284. Span.stringEmpty(LOCALE.NUMBERS.wordOrdinal(parseInt(node.textContent, 10)))
  285. ];
  286. }