visitor.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Implements a class that computes complexities for enriched math
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {MmlNode, AbstractMmlTokenNode} from '../../core/MmlTree/MmlNode.js';
  23. import {MmlMroot} from '../../core/MmlTree/MmlNodes/mroot.js';
  24. import {MmlMaction} from '../../core/MmlTree/MmlNodes/maction.js';
  25. import {MmlMsubsup, MmlMsub, MmlMsup} from '../../core/MmlTree/MmlNodes/msubsup.js';
  26. import {MmlMunderover, MmlMunder, MmlMover} from '../../core/MmlTree/MmlNodes/munderover.js';
  27. import {MmlVisitor} from '../../core/MmlTree/MmlVisitor.js';
  28. import {MmlFactory} from '../../core/MmlTree/MmlFactory.js';
  29. import {Collapse} from './collapse.js';
  30. import {OptionList, userOptions, defaultOptions} from '../../util/Options.js';
  31. /*==========================================================================*/
  32. /**
  33. * A visitor pattern that computes complexities within the MathML tree
  34. */
  35. export class ComplexityVisitor extends MmlVisitor {
  36. /**
  37. * The options for handling collapsing
  38. */
  39. public static OPTIONS: OptionList = {
  40. identifyCollapsible: true, // mark elements that should be collapsed
  41. makeCollapsible: true, // insert maction to allow collapsing
  42. Collapse: Collapse // the Collapse class to use
  43. };
  44. /**
  45. * Values used to compute complexities
  46. */
  47. public complexity: {[name: string]: number} = {
  48. text: .5, // each character of a token element adds this to complexity
  49. token: .5, // each token element gets this additional complexity
  50. child: 1, // child nodes add this to their parent node's complexity
  51. script: .8, // script elements reduce their complexity by this factor
  52. sqrt: 2, // sqrt adds this extra complexity
  53. subsup: 2, // sub-sup adds this extra complexity
  54. underover: 2, // under-over adds this extra complexity
  55. fraction: 2, // fractions add this extra complexity
  56. enclose: 2, // menclose adds this extra complexity
  57. action: 2, // maction adds this extra complexity
  58. phantom: 0, // mphantom makes complexity 0?
  59. xml: 2, // Can't really measure complexity of annotation-xml, so punt
  60. glyph: 2 // Can't really measure complexity of mglyph, to punt
  61. };
  62. /**
  63. * The object used to handle collapsable content
  64. */
  65. public collapse: Collapse;
  66. /**
  67. * The MmlFactory for this visitor
  68. */
  69. public factory: MmlFactory;
  70. /**
  71. * The options for this visitor
  72. */
  73. public options: OptionList;
  74. /**
  75. * @override
  76. */
  77. constructor(factory: MmlFactory, options: OptionList) {
  78. super(factory);
  79. let CLASS = this.constructor as typeof ComplexityVisitor;
  80. this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
  81. this.collapse = new this.options.Collapse(this);
  82. this.factory = factory;
  83. }
  84. /*==========================================================================*/
  85. /**
  86. * @override
  87. */
  88. public visitTree(node: MmlNode) {
  89. super.visitTree(node, true);
  90. if (this.options.makeCollapsible) {
  91. this.collapse.makeCollapse(node);
  92. }
  93. }
  94. /**
  95. * @override
  96. */
  97. public visitNode(node: MmlNode, save: boolean) {
  98. if (node.attributes.get('data-semantic-complexity')) return;
  99. return super.visitNode(node, save);
  100. }
  101. /**
  102. * For token nodes, use the content length, otherwise, add up the child complexities
  103. *
  104. * @override
  105. */
  106. public visitDefault(node: MmlNode, save: boolean) {
  107. let complexity;
  108. if (node.isToken) {
  109. const text = (node as AbstractMmlTokenNode).getText();
  110. complexity = this.complexity.text * text.length + this.complexity.token;
  111. } else {
  112. complexity = this.childrenComplexity(node);
  113. }
  114. return this.setComplexity(node, complexity, save);
  115. }
  116. /**
  117. * For a fraction, add the complexities of the children and scale by script factor, then
  118. * add the fraction amount
  119. *
  120. * @param {MmlNode} node The node whose complixity is being computed
  121. * @param {boolean} save True if the complexity is to be saved or just returned
  122. */
  123. protected visitMfracNode(node: MmlNode, save: boolean) {
  124. const complexity = this.childrenComplexity(node) * this.complexity.script + this.complexity.fraction;
  125. return this.setComplexity(node, complexity, save);
  126. }
  127. /**
  128. * For square roots, use the child complexity plus the sqrt complexity
  129. *
  130. * @param {MmlNode} node The node whose complixity is being computed
  131. * @param {boolean} save True if the complexity is to be saved or just returned
  132. */
  133. protected visitMsqrtNode(node: MmlNode, save: boolean) {
  134. const complexity = this.childrenComplexity(node) + this.complexity.sqrt;
  135. return this.setComplexity(node, complexity, save);
  136. }
  137. /**
  138. * For roots, do the sqrt root computation and remove a bit for the root
  139. * (since it is counted in the children sum but is smaller)
  140. *
  141. * @param {MmlMroot} node The node whose complixity is being computed
  142. * @param {boolean} save True if the complexity is to be saved or just returned
  143. */
  144. protected visitMrootNode(node: MmlMroot, save: boolean) {
  145. const complexity = this.childrenComplexity(node) + this.complexity.sqrt
  146. - (1 - this.complexity.script) * this.getComplexity(node.childNodes[1]);
  147. return this.setComplexity(node, complexity, save);
  148. }
  149. /**
  150. * Phantom complexity is 0
  151. *
  152. * @param {MmlNode} node The node whose complixity is being computed
  153. * @param {boolean} save True if the complexity is to be saved or just returned
  154. */
  155. protected visitMphantomNode(node: MmlNode, save: boolean) {
  156. return this.setComplexity(node, this.complexity.phantom, save);
  157. }
  158. /**
  159. * For ms, add the complexity of the quotes to that of the content, and use the
  160. * length of that times the text factor as the complexity
  161. *
  162. * @param {MmlNode} node The node whose complixity is being computed
  163. * @param {boolean} save True if the complexity is to be saved or just returned
  164. */
  165. protected visitMsNode(node: MmlNode, save: boolean) {
  166. const text = node.attributes.get('lquote')
  167. + (node as AbstractMmlTokenNode).getText()
  168. + node.attributes.get('rquote');
  169. const complexity = text.length * this.complexity.text;
  170. return this.setComplexity(node, complexity, save);
  171. }
  172. /**
  173. * For supscripts and superscripts use the maximum of the script complexities,
  174. * multiply by the script factor, and add the base complexity. Add the child
  175. * complexity for each child, and the subsup complexity.
  176. *
  177. * @param {MmlMsubsup} node The node whose complixity is being computed
  178. * @param {boolean} save True if the complexity is to be saved or just returned
  179. */
  180. protected visitMsubsupNode(node: MmlMsubsup, save: boolean) {
  181. super.visitDefault(node, true);
  182. const sub = node.childNodes[node.sub];
  183. const sup = node.childNodes[node.sup];
  184. const base = node.childNodes[node.base];
  185. let complexity = Math.max(
  186. sub ? this.getComplexity(sub) : 0,
  187. sup ? this.getComplexity(sup) : 0
  188. ) * this.complexity.script;
  189. complexity += this.complexity.child * ((sub ? 1 : 0) + (sup ? 1 : 0));
  190. complexity += (base ? this.getComplexity(base) + this.complexity.child : 0);
  191. complexity += this.complexity.subsup;
  192. return this.setComplexity(node, complexity, save);
  193. }
  194. /**
  195. * @param {MmlMsub} node The node whose complixity is being computed
  196. * @param {boolean} save True if the complexity is to be saved or just returned
  197. */
  198. protected visitMsubNode(node: MmlMsub, save: boolean) {
  199. return this.visitMsubsupNode(node, save);
  200. }
  201. /**
  202. * @param {MmlMsup} node The node whose complixity is being computed
  203. * @param {boolean} save True if the complexity is to be saved or just returned
  204. */
  205. protected visitMsupNode(node: MmlMsup, save: boolean) {
  206. return this.visitMsubsupNode(node, save);
  207. }
  208. /**
  209. * For under/over, get the maximum of the complexities of the under and over
  210. * elements times the script factor, and that the maximum of that with the
  211. * base complexity. Add child complexity for all children, and add the
  212. * underover amount.
  213. *
  214. * @param {MmlMunderover} node The node whose complixity is being computed
  215. * @param {boolean} save True if the complexity is to be saved or just returned
  216. */
  217. protected visitMunderoverNode(node: MmlMunderover, save: boolean) {
  218. super.visitDefault(node, true);
  219. const under = node.childNodes[node.under];
  220. const over = node.childNodes[node.over];
  221. const base = node.childNodes[node.base];
  222. let complexity = Math.max(
  223. under ? this.getComplexity(under) : 0,
  224. over ? this.getComplexity(over) : 0,
  225. ) * this.complexity.script;
  226. if (base) {
  227. complexity = Math.max(this.getComplexity(base), complexity);
  228. }
  229. complexity += this.complexity.child * ((under ? 1 : 0) + (over ? 1 : 0) + (base ? 1 : 0));
  230. complexity += this.complexity.underover;
  231. return this.setComplexity(node, complexity, save);
  232. }
  233. /**
  234. * @param {MmlMunder} node The node whose complixity is being computed
  235. * @param {boolean} save True if the complexity is to be saved or just returned
  236. */
  237. protected visitMunderNode(node: MmlMunder, save: boolean) {
  238. return this.visitMunderoverNode(node, save);
  239. }
  240. /**
  241. * @param {MmlMover} node The node whose complixity is being computed
  242. * @param {boolean} save True if the complexity is to be saved or just returned
  243. */
  244. protected visitMoverNode(node: MmlMover, save: boolean) {
  245. return this.visitMunderoverNode(node, save);
  246. }
  247. /**
  248. * For enclose, use sum of child complexities plus some for the enclose
  249. *
  250. * @param {MmlNode} node The node whose complixity is being computed
  251. * @param {boolean} save True if the complexity is to be saved or just returned
  252. */
  253. protected visitMencloseNode(node: MmlNode, save: boolean) {
  254. const complexity = this.childrenComplexity(node) + this.complexity.enclose;
  255. return this.setComplexity(node, complexity, save);
  256. }
  257. /**
  258. * For actions, use the complexity of the selected child
  259. *
  260. * @param {MmlMaction} node The node whose complixity is being computed
  261. * @param {boolean} save True if the complexity is to be saved or just returned
  262. */
  263. protected visitMactionNode(node: MmlMaction, save: boolean) {
  264. this.childrenComplexity(node);
  265. const complexity = this.getComplexity(node.selected);
  266. return this.setComplexity(node, complexity, save);
  267. }
  268. /**
  269. * For semantics, get the complexity from the first child
  270. *
  271. * @param {MmlNode} node The node whose complixity is being computed
  272. * @param {boolean} save True if the complexity is to be saved or just returned
  273. */
  274. protected visitMsemanticsNode(node: MmlNode, save: boolean) {
  275. const child = node.childNodes[0] as MmlNode;
  276. let complexity = 0;
  277. if (child) {
  278. this.visitNode(child, true);
  279. complexity = this.getComplexity(child);
  280. }
  281. return this.setComplexity(node, complexity, save);
  282. }
  283. /**
  284. * Can't really measure annotations, so just use a specific value
  285. *
  286. * @param {MmlNode} node The node whose complixity is being computed
  287. * @param {boolean} save True if the complexity is to be saved or just returned
  288. */
  289. protected visitAnnotationNode(node: MmlNode, save: boolean) {
  290. return this.setComplexity(node, this.complexity.xml, save);
  291. }
  292. /**
  293. * Can't really measure annotations, so just use a specific value
  294. *
  295. * @param {MmlNode} node The node whose complixity is being computed
  296. * @param {boolean} save True if the complexity is to be saved or just returned
  297. */
  298. protected visitAnnotation_xmlNode(node: MmlNode, save: boolean) {
  299. return this.setComplexity(node, this.complexity.xml, save);
  300. }
  301. /**
  302. * Can't really measure mglyph complexity, so just use a specific value
  303. *
  304. * @param {MmlNode} node The node whose complixity is being computed
  305. * @param {boolean} save True if the complexity is to be saved or just returned
  306. */
  307. protected visitMglyphNode(node: MmlNode, save: boolean) {
  308. return this.setComplexity(node, this.complexity.glyph, save);
  309. }
  310. /*==========================================================================*/
  311. /**
  312. * @param {MmlNode} node The node whose complixity is needed
  313. * @return {number} The complexity fof the node (if collapsable, then the collapsed complexity)
  314. */
  315. public getComplexity(node: MmlNode): number {
  316. const collapsed = node.getProperty('collapsedComplexity');
  317. return (collapsed != null ? collapsed : node.attributes.get('data-semantic-complexity')) as number;
  318. }
  319. /**
  320. * @param {MmlNode} node The node whose complixity is being set
  321. * @param {complexity} number The complexity for the node
  322. * @param {boolean} save True if complexity is to be set or just reported
  323. */
  324. protected setComplexity(node: MmlNode, complexity: number, save: boolean) {
  325. if (save) {
  326. if (this.options.identifyCollapsible) {
  327. complexity = this.collapse.check(node, complexity);
  328. }
  329. node.attributes.set('data-semantic-complexity', complexity);
  330. }
  331. return complexity;
  332. }
  333. /**
  334. * @param {MmlNode} node The node whose children complexities are to be added
  335. * @return {number} The sum of the complexities, plus child complexity for each one
  336. */
  337. protected childrenComplexity(node: MmlNode): number {
  338. super.visitDefault(node, true);
  339. let complexity = 0;
  340. for (const child of node.childNodes) {
  341. complexity += this.getComplexity(child as MmlNode);
  342. }
  343. if (node.childNodes.length > 1) {
  344. complexity += node.childNodes.length * this.complexity.child;
  345. }
  346. return complexity;
  347. }
  348. }