MmlNode.ts 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-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 Interfaces and abstract classes for MmlNode objects
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {Attributes, INHERIT} from './Attributes.js';
  23. import {Property, PropertyList, Node, AbstractNode, AbstractEmptyNode, NodeClass} from '../Tree/Node.js';
  24. import {MmlFactory} from './MmlFactory.js';
  25. import {DOMAdaptor} from '../DOMAdaptor.js';
  26. /**
  27. * Used in setInheritedAttributes() to pass originating node kind as well as property value
  28. */
  29. export type AttributeList = {[attribute: string]: [string, Property]};
  30. /**
  31. * These are the TeX classes for spacing computations
  32. */
  33. export const TEXCLASS = {
  34. ORD: 0,
  35. OP: 1,
  36. BIN: 2,
  37. REL: 3,
  38. OPEN: 4,
  39. CLOSE: 5,
  40. PUNCT: 6,
  41. INNER: 7,
  42. VCENTER: 8, // Used in TeXAtom, but not for spacing
  43. NONE: -1
  44. };
  45. export const TEXCLASSNAMES = ['ORD', 'OP', 'BIN', 'REL', 'OPEN', 'CLOSE', 'PUNCT', 'INNER', 'VCENTER'];
  46. /**
  47. * The spacing sizes used by the TeX spacing table below.
  48. */
  49. const TEXSPACELENGTH = ['', 'thinmathspace', 'mediummathspace', 'thickmathspace'];
  50. /**
  51. * See TeXBook Chapter 18 (p. 170)
  52. */
  53. const TEXSPACE = [
  54. [ 0, -1, 2, 3, 0, 0, 0, 1], // ORD
  55. [-1, -1, 0, 3, 0, 0, 0, 1], // OP
  56. [ 2, 2, 0, 0, 2, 0, 0, 2], // BIN
  57. [ 3, 3, 0, 0, 3, 0, 0, 3], // REL
  58. [ 0, 0, 0, 0, 0, 0, 0, 0], // OPEN
  59. [ 0, -1, 2, 3, 0, 0, 0, 1], // CLOSE
  60. [ 1, 1, 0, 1, 1, 1, 1, 1], // PUNCT
  61. [ 1, -1, 2, 3, 1, 0, 1, 1] // INNER
  62. ];
  63. /**
  64. * Attributes used to determine indentation and shifting
  65. */
  66. export const indentAttributes = [
  67. 'indentalign', 'indentalignfirst',
  68. 'indentshift', 'indentshiftfirst'
  69. ];
  70. /**
  71. * The nodes that can be in the internal MathML tree
  72. */
  73. export type MMLNODE = MmlNode | TextNode | XMLNode;
  74. /*****************************************************************/
  75. /**
  76. * The MmlNode interface (extends Node interface)
  77. */
  78. export interface MmlNode extends Node {
  79. /**
  80. * Test various properties of MathML nodes
  81. */
  82. readonly isToken: boolean;
  83. readonly isEmbellished: boolean;
  84. readonly isSpacelike: boolean;
  85. readonly linebreakContainer: boolean;
  86. readonly hasNewLine: boolean;
  87. /**
  88. * The expected number of children (-1 means use inferred mrow)
  89. */
  90. readonly arity: number;
  91. readonly isInferred: boolean;
  92. /**
  93. * Get the parent node (skipping inferred mrows and
  94. * other nodes marked as notParent)
  95. */
  96. readonly Parent: MmlNode;
  97. readonly notParent: boolean;
  98. /**
  99. * The actual parent in the tree
  100. */
  101. parent: MmlNode;
  102. /**
  103. * values needed for TeX spacing computations
  104. */
  105. texClass: number;
  106. prevClass: number;
  107. prevLevel: number;
  108. /**
  109. * The attributes (explicit and inherited) for this node
  110. */
  111. attributes: Attributes;
  112. /**
  113. * @return {MmlNode} For embellished operators, the child node that contains the
  114. * core <mo> node. For non-embellished nodes, the original node.
  115. */
  116. core(): MmlNode;
  117. /**
  118. * @return {MmlNode} For embellished operators, the core <mo> element (at whatever
  119. * depth). For non-embellished nodes, the original node itself.
  120. */
  121. coreMO(): MmlNode;
  122. /**
  123. * @return {number} For embellished operators, the index of the child node containing
  124. * the core <mo>. For non-embellished nodes, 0.
  125. */
  126. coreIndex(): number;
  127. /**
  128. * @return {number} The index of this node in its parent's childNodes array.
  129. */
  130. childPosition(): number;
  131. /**
  132. * @param {MmlNode} prev The node that is before this one for TeX spacing purposes
  133. * (not all nodes count in TeX measurements)
  134. * @return {MmlNode} The node that should be the previous node for the next one
  135. * in the tree (usually, either the last child, or the node itself)
  136. */
  137. setTeXclass(prev: MmlNode): MmlNode;
  138. /**
  139. * @return {string} The spacing to use before this element (one of TEXSPACELENGTH array above)
  140. */
  141. texSpacing(): string;
  142. /**
  143. * @return {boolean} The core mo element has an explicit 'form', 'lspace', or 'rspace' attribute
  144. */
  145. hasSpacingAttributes(): boolean;
  146. /**
  147. * Sets the nodes inherited attributes, and pushes them to the nodes children.
  148. *
  149. * @param {AttributeList} attributes The list of inheritable attributes (with the node kinds
  150. * from which they came)
  151. * @param {boolean} display The displaystyle to inherit
  152. * @param {number} level The scriptlevel to inherit
  153. * @param {boolean} prime The TeX prime style to inherit (T vs. T', etc).
  154. */
  155. setInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean): void;
  156. /**
  157. * Set the nodes inherited attributes based on the attributes of the given node
  158. * (used for creating extra nodes in the tree after setInheritedAttributes has already run)
  159. *
  160. * @param {MmlNode} node The node whose attributes are to be used as a template
  161. */
  162. inheritAttributesFrom(node: MmlNode): void;
  163. /**
  164. * Replace the current node with an error message (or the name of the node)
  165. *
  166. * @param {string} message The error message to use
  167. * @param {PropertyList} options The options telling how much to verify
  168. * @param {boolean} short True means use just the kind if not using full errors
  169. * @return {MmlNode} The construted merror
  170. */
  171. mError(message: string, options: PropertyList, short?: boolean): MmlNode;
  172. /**
  173. * Check integrity of MathML structure
  174. *
  175. * @param {PropertyList} options The options controlling the check
  176. */
  177. verifyTree(options?: PropertyList): void;
  178. }
  179. /*****************************************************************/
  180. /**
  181. * The MmlNode class interface (extends the NodeClass)
  182. */
  183. export interface MmlNodeClass extends NodeClass {
  184. /**
  185. * The list of default attribute values for nodes of this class
  186. */
  187. defaults?: PropertyList;
  188. /**
  189. * An MmlNode takes a NodeFactory (so it can create additional nodes as needed), a list
  190. * of attributes, and an array of children and returns the desired MmlNode with
  191. * those attributes and children
  192. *
  193. * @constructor
  194. * @param {MmlFactory} factory The MathML node factory to use to create additional nodes
  195. * @param {PropertyList} attributes The list of initial attributes for the node
  196. * @param {MmlNode[]} children The initial child nodes (more can be added later)
  197. */
  198. new (factory: MmlFactory, attributes?: PropertyList, children?: MmlNode[]): MmlNode;
  199. }
  200. /*****************************************************************/
  201. /**
  202. * The abstract MmlNode class (extends the AbstractNode class and implements
  203. * the IMmlNode interface)
  204. */
  205. export abstract class AbstractMmlNode extends AbstractNode implements MmlNode {
  206. /**
  207. * The properties common to all MathML nodes
  208. */
  209. public static defaults: PropertyList = {
  210. mathbackground: INHERIT,
  211. mathcolor: INHERIT,
  212. mathsize: INHERIT, // technically only for token elements, but <mstyle mathsize="..."> should
  213. // scale all spaces, fractions, etc.
  214. dir: INHERIT
  215. };
  216. /**
  217. * This lists properties that do NOT get inherited between specific kinds
  218. * of nodes. The outer keys are the node kinds that are being inherited FROM,
  219. * while the second level of keys are the nodes that INHERIT the values. Any
  220. * property appearing in the innermost list is NOT inherited by the pair.
  221. *
  222. * For example, an mpadded element will not inherit a width attribute from an mstyle node.
  223. */
  224. public static noInherit: {[node1: string]: {[node2: string]: {[attribute: string]: boolean}}} = {
  225. mstyle: {
  226. mpadded: {width: true, height: true, depth: true, lspace: true, voffset: true},
  227. mtable: {width: true, height: true, depth: true, align: true}
  228. },
  229. maligngroup: {
  230. mrow: {groupalign: true},
  231. mtable: {groupalign: true}
  232. }
  233. };
  234. /**
  235. * This lists the attributes that should always be inherited,
  236. * even when there is no default value for the attribute.
  237. */
  238. public static alwaysInherit: {[name: string]: boolean} = {
  239. scriptminsize: true,
  240. scriptsizemultiplier: true
  241. };
  242. /**
  243. * This is the list of options for the verifyTree() method
  244. */
  245. public static verifyDefaults: PropertyList = {
  246. checkArity: true,
  247. checkAttributes: false,
  248. fullErrors: false,
  249. fixMmultiscripts: true,
  250. fixMtables: true
  251. };
  252. /*
  253. * These default to being unset (the node doesn't participate in spacing calculations).
  254. * The correct values are produced when the setTeXclass() method is called on the tree.
  255. */
  256. /**
  257. * The TeX class for the preceding node
  258. */
  259. public prevClass: number = null;
  260. /**
  261. * The scriptlevel of the preceding node
  262. */
  263. public prevLevel: number = null;
  264. /**
  265. * This node's attributes
  266. */
  267. public attributes: Attributes;
  268. /**
  269. * Child nodes are MmlNodes (special case of Nodes).
  270. */
  271. public childNodes: MmlNode[];
  272. /**
  273. * The parent is an MmlNode
  274. */
  275. public parent: MmlNode;
  276. /**
  277. * The node factory is an MmlFactory
  278. */
  279. public readonly factory: MmlFactory;
  280. /**
  281. * The TeX class of this node (obtained via texClass below)
  282. */
  283. protected texclass: number = null;
  284. /**
  285. * Create an MmlNode:
  286. * If the arity is -1, add the inferred row (created by the factory)
  287. * Add the children, if any
  288. * Create the Attribute object from the class defaults and the global defaults (the math node defaults)
  289. *
  290. * @override
  291. */
  292. constructor(factory: MmlFactory, attributes: PropertyList = {}, children: MmlNode[] = []) {
  293. super(factory);
  294. if (this.arity < 0) {
  295. this.childNodes = [factory.create('inferredMrow')];
  296. this.childNodes[0].parent = this;
  297. }
  298. this.setChildren(children);
  299. this.attributes = new Attributes(
  300. factory.getNodeClass(this.kind).defaults,
  301. factory.getNodeClass('math').defaults
  302. );
  303. this.attributes.setList(attributes);
  304. }
  305. /**
  306. * @override
  307. *
  308. * @param {boolean} keepIds True to copy id attributes, false to skip them.
  309. * (May cause error in the future, since not part of the interface.)
  310. * @return {AbstractMmlNode} The copied node tree.
  311. */
  312. public copy(keepIds: boolean = false): AbstractMmlNode {
  313. const node = this.factory.create(this.kind) as AbstractMmlNode;
  314. node.properties = {...this.properties};
  315. if (this.attributes) {
  316. const attributes = this.attributes.getAllAttributes();
  317. for (const name of Object.keys(attributes)) {
  318. if (name !== 'id' || keepIds) {
  319. node.attributes.set(name, attributes[name]);
  320. }
  321. }
  322. }
  323. if (this.childNodes && this.childNodes.length) {
  324. let children = this.childNodes as MmlNode[];
  325. if (children.length === 1 && children[0].isInferred) {
  326. children = children[0].childNodes as MmlNode[];
  327. }
  328. for (const child of children) {
  329. if (child) {
  330. node.appendChild(child.copy() as MmlNode);
  331. } else {
  332. node.childNodes.push(null);
  333. }
  334. }
  335. }
  336. return node;
  337. }
  338. /**
  339. * The TeX class for this node
  340. */
  341. public get texClass(): number {
  342. return this.texclass;
  343. }
  344. /**
  345. * The TeX class for this node
  346. */
  347. public set texClass(texClass: number) {
  348. this.texclass = texClass;
  349. }
  350. /**
  351. * @return {boolean} true if this is a token node
  352. */
  353. public get isToken(): boolean {
  354. return false;
  355. }
  356. /**
  357. * @return {boolean} true if this is an embellished operator
  358. */
  359. public get isEmbellished(): boolean {
  360. return false;
  361. }
  362. /**
  363. * @return {boolean} true if this is a space-like node
  364. */
  365. public get isSpacelike(): boolean {
  366. return false;
  367. }
  368. /**
  369. * @return {boolean} true if this is a node that supports linebreaks in its children
  370. */
  371. public get linebreakContainer(): boolean {
  372. return false;
  373. }
  374. /**
  375. * @return {boolean} true if this node contains a line break
  376. */
  377. public get hasNewLine(): boolean {
  378. return false;
  379. }
  380. /**
  381. * @return {number} The number of children allowed, or Infinity for any number,
  382. * or -1 for when an inferred row is needed for the children.
  383. * Special case is 1, meaning at least one (other numbers
  384. * mean exactly that many).
  385. */
  386. public get arity(): number {
  387. return Infinity;
  388. }
  389. /**
  390. * @return {boolean} true if this is an inferred mrow
  391. */
  392. public get isInferred(): boolean {
  393. return false;
  394. }
  395. /**
  396. * @return {MmlNode} The logical parent of this node (skipping over inferred rows
  397. * some other node types)
  398. */
  399. public get Parent(): MmlNode {
  400. let parent = this.parent;
  401. while (parent && parent.notParent) {
  402. parent = parent.Parent;
  403. }
  404. return parent;
  405. }
  406. /**
  407. * @return {boolean} true if this is a node that doesn't count as a parent node in Parent()
  408. */
  409. public get notParent(): boolean {
  410. return false;
  411. }
  412. /**
  413. * If there is an inferred row, the the children of that instead
  414. *
  415. * @override
  416. */
  417. public setChildren(children: MmlNode[]) {
  418. if (this.arity < 0) {
  419. return this.childNodes[0].setChildren(children);
  420. }
  421. return super.setChildren(children);
  422. }
  423. /**
  424. * If there is an inferred row, append to that instead.
  425. * If a child is inferred, append its children instead.
  426. *
  427. * @override
  428. */
  429. public appendChild(child: MmlNode) {
  430. if (this.arity < 0) {
  431. this.childNodes[0].appendChild(child);
  432. return child;
  433. }
  434. if (child.isInferred) {
  435. //
  436. // If we can have arbitrary children, remove the inferred mrow
  437. // (just add its children).
  438. //
  439. if (this.arity === Infinity) {
  440. child.childNodes.forEach((node) => super.appendChild(node));
  441. return child;
  442. }
  443. //
  444. // Otherwise, convert the inferred mrow to an explicit mrow
  445. //
  446. const original = child;
  447. child = this.factory.create('mrow');
  448. child.setChildren(original.childNodes);
  449. child.attributes = original.attributes;
  450. for (const name of original.getPropertyNames()) {
  451. child.setProperty(name, original.getProperty(name));
  452. }
  453. }
  454. return super.appendChild(child);
  455. }
  456. /**
  457. * If there is an inferred row, remove the child from there
  458. *
  459. * @override
  460. */
  461. public replaceChild(newChild: MmlNode, oldChild: MmlNode) {
  462. if (this.arity < 0) {
  463. this.childNodes[0].replaceChild(newChild, oldChild);
  464. return newChild;
  465. }
  466. return super.replaceChild(newChild, oldChild);
  467. }
  468. /**
  469. * @override
  470. */
  471. public core(): MmlNode {
  472. return this;
  473. }
  474. /**
  475. * @override
  476. */
  477. public coreMO(): MmlNode {
  478. return this;
  479. }
  480. /**
  481. * @override
  482. */
  483. public coreIndex() {
  484. return 0;
  485. }
  486. /**
  487. * @override
  488. */
  489. public childPosition() {
  490. let child: MmlNode = this;
  491. let parent = child.parent;
  492. while (parent && parent.notParent) {
  493. child = parent;
  494. parent = parent.parent;
  495. }
  496. if (parent) {
  497. let i = 0;
  498. for (const node of parent.childNodes) {
  499. if (node === child) {
  500. return i;
  501. }
  502. i++;
  503. }
  504. }
  505. return null;
  506. }
  507. /**
  508. * @override
  509. */
  510. public setTeXclass(prev: MmlNode): MmlNode {
  511. this.getPrevClass(prev);
  512. return (this.texClass != null ? this : prev);
  513. }
  514. /**
  515. * For embellished operators, get the data from the core and clear the core
  516. *
  517. * @param {MmlNode} core The core <mo> for this node
  518. */
  519. protected updateTeXclass(core: MmlNode) {
  520. if (core) {
  521. this.prevClass = core.prevClass;
  522. this.prevLevel = core.prevLevel;
  523. core.prevClass = core.prevLevel = null;
  524. this.texClass = core.texClass;
  525. }
  526. }
  527. /**
  528. * Get the previous element's texClass and scriptlevel
  529. *
  530. * @param {MmlNode} prev The previous node to this one
  531. */
  532. protected getPrevClass(prev: MmlNode) {
  533. if (prev) {
  534. this.prevClass = prev.texClass;
  535. this.prevLevel = prev.attributes.get('scriptlevel') as number;
  536. }
  537. }
  538. /**
  539. * @return {string} returns the spacing to use before this node
  540. */
  541. public texSpacing(): string {
  542. let prevClass = (this.prevClass != null ? this.prevClass : TEXCLASS.NONE);
  543. let texClass = this.texClass || TEXCLASS.ORD;
  544. if (prevClass === TEXCLASS.NONE || texClass === TEXCLASS.NONE) {
  545. return '';
  546. }
  547. if (prevClass === TEXCLASS.VCENTER) {
  548. prevClass = TEXCLASS.ORD;
  549. }
  550. if (texClass === TEXCLASS.VCENTER) {
  551. texClass = TEXCLASS.ORD;
  552. }
  553. let space = TEXSPACE[prevClass][texClass];
  554. if ((this.prevLevel > 0 || this.attributes.get('scriptlevel') > 0) && space >= 0) {
  555. return '';
  556. }
  557. return TEXSPACELENGTH[Math.abs(space)];
  558. }
  559. /**
  560. * @return {boolean} The core mo element has an explicit 'form' attribute
  561. */
  562. public hasSpacingAttributes(): boolean {
  563. return this.isEmbellished && this.coreMO().hasSpacingAttributes();
  564. }
  565. /**
  566. * Sets the inherited propertis for this node, and pushes inherited properties to the children
  567. *
  568. * For each inheritable attribute:
  569. * If the node has a default for this attribute, try to inherit it
  570. * but check if the noInherit object prevents that.
  571. * If the node doesn't have an explicit displaystyle, inherit it
  572. * If the node doesn't have an explicit scriptstyle, inherit it
  573. * If the prime style is true, set it as a property (it is not a MathML attribute)
  574. * Check that the number of children is correct
  575. * Finally, push any inherited attributes to teh children.
  576. *
  577. * @override
  578. */
  579. public setInheritedAttributes(attributes: AttributeList = {},
  580. display: boolean = false, level: number = 0, prime: boolean = false) {
  581. let defaults = this.attributes.getAllDefaults();
  582. for (const key of Object.keys(attributes)) {
  583. if (defaults.hasOwnProperty(key) || AbstractMmlNode.alwaysInherit.hasOwnProperty(key)) {
  584. let [node, value] = attributes[key];
  585. let noinherit = (AbstractMmlNode.noInherit[node] || {})[this.kind] || {};
  586. if (!noinherit[key]) {
  587. this.attributes.setInherited(key, value);
  588. }
  589. }
  590. }
  591. let displaystyle = this.attributes.getExplicit('displaystyle');
  592. if (displaystyle === undefined) {
  593. this.attributes.setInherited('displaystyle', display);
  594. }
  595. let scriptlevel = this.attributes.getExplicit('scriptlevel');
  596. if (scriptlevel === undefined) {
  597. this.attributes.setInherited('scriptlevel', level);
  598. }
  599. if (prime) {
  600. this.setProperty('texprimestyle', prime);
  601. }
  602. let arity = this.arity;
  603. if (arity >= 0 && arity !== Infinity && ((arity === 1 && this.childNodes.length === 0) ||
  604. (arity !== 1 && this.childNodes.length !== arity))) {
  605. //
  606. // Make sure there are the right number of child nodes
  607. // (trim them or add empty mrows)
  608. //
  609. if (arity < this.childNodes.length) {
  610. this.childNodes = this.childNodes.slice(0, arity);
  611. } else {
  612. while (this.childNodes.length < arity) {
  613. this.appendChild(this.factory.create('mrow'));
  614. }
  615. }
  616. }
  617. this.setChildInheritedAttributes(attributes, display, level, prime);
  618. }
  619. /**
  620. * Apply inherited attributes to all children
  621. * (Some classes override this to handle changes in displaystyle and scriptlevel)
  622. *
  623. * @param {AttributeList} attributes The list of inheritable attributes (with the node kinds
  624. * from which they came)
  625. * @param {boolean} display The displaystyle to inherit
  626. * @param {number} level The scriptlevel to inherit
  627. * @param {boolean} prime The TeX prime style to inherit (T vs. T', etc).
  628. */
  629. protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
  630. for (const child of this.childNodes) {
  631. child.setInheritedAttributes(attributes, display, level, prime);
  632. }
  633. }
  634. /**
  635. * Used by subclasses to add their own attributes to the inherited list
  636. * (e.g., mstyle uses this to augment the inherited attibutes)
  637. *
  638. * @param {AttributeList} current The current list of inherited attributes
  639. * @param {PropertyList} attributes The new attributes to add into the list
  640. */
  641. protected addInheritedAttributes(current: AttributeList, attributes: PropertyList) {
  642. let updated: AttributeList = {...current};
  643. for (const name of Object.keys(attributes)) {
  644. if (name !== 'displaystyle' && name !== 'scriptlevel' && name !== 'style') {
  645. updated[name] = [this.kind, attributes[name]];
  646. }
  647. }
  648. return updated;
  649. }
  650. /**
  651. * Set the nodes inherited attributes based on the attributes of the given node
  652. * (used for creating extra nodes in the tree after setInheritedAttributes has already run)
  653. *
  654. * @param {MmlNode} node The node whose attributes are to be used as a template
  655. */
  656. public inheritAttributesFrom(node: MmlNode) {
  657. const attributes = node.attributes;
  658. const display = attributes.get('displaystyle') as boolean;
  659. const scriptlevel = attributes.get('scriptlevel') as number;
  660. const defaults: AttributeList = (!attributes.isSet('mathsize') ? {} : {
  661. mathsize: ['math', attributes.get('mathsize')]
  662. });
  663. const prime = node.getProperty('texprimestyle') as boolean || false;
  664. this.setInheritedAttributes(defaults, display, scriptlevel, prime);
  665. }
  666. /**
  667. * Verify the attributes, and that there are the right number of children.
  668. * Then verify the children.
  669. *
  670. * @param {PropertyList} options The options telling how much to verify
  671. */
  672. public verifyTree(options: PropertyList = null) {
  673. if (options === null) {
  674. return;
  675. }
  676. this.verifyAttributes(options);
  677. let arity = this.arity;
  678. if (options['checkArity']) {
  679. if (arity >= 0 && arity !== Infinity &&
  680. ((arity === 1 && this.childNodes.length === 0) ||
  681. (arity !== 1 && this.childNodes.length !== arity))) {
  682. this.mError('Wrong number of children for "' + this.kind + '" node', options, true);
  683. }
  684. }
  685. this.verifyChildren(options);
  686. }
  687. /**
  688. * Verify that all the attributes are valid (i.e., have defaults)
  689. *
  690. * @param {PropertyList} options The options telling how much to verify
  691. */
  692. protected verifyAttributes(options: PropertyList) {
  693. if (options['checkAttributes']) {
  694. const attributes = this.attributes;
  695. const bad = [];
  696. for (const name of attributes.getExplicitNames()) {
  697. if (name.substr(0, 5) !== 'data-' && attributes.getDefault(name) === undefined &&
  698. !name.match(/^(?:class|style|id|(?:xlink:)?href)$/)) {
  699. // FIXME: provide a configurable checker for names that are OK
  700. bad.push(name);
  701. }
  702. // FIXME: add ability to check attribute values?
  703. }
  704. if (bad.length) {
  705. this.mError('Unknown attributes for ' + this.kind + ' node: ' + bad.join(', '), options);
  706. }
  707. }
  708. }
  709. /**
  710. * Verify the children.
  711. *
  712. * @param {PropertyList} options The options telling how much to verify
  713. */
  714. protected verifyChildren(options: PropertyList) {
  715. for (const child of this.childNodes) {
  716. child.verifyTree(options);
  717. }
  718. }
  719. /**
  720. * Replace the current node with an error message (or the name of the node)
  721. *
  722. * @param {string} message The error message to use
  723. * @param {PropertyList} options The options telling how much to verify
  724. * @param {boolean} short True means use just the kind if not using full errors
  725. * @return {MmlNode} The constructed merror
  726. */
  727. public mError(message: string, options: PropertyList, short: boolean = false): MmlNode {
  728. if (this.parent && this.parent.isKind('merror')) {
  729. return null;
  730. }
  731. let merror = this.factory.create('merror');
  732. merror.attributes.set('data-mjx-message', message);
  733. if (options['fullErrors'] || short) {
  734. let mtext = this.factory.create('mtext');
  735. let text = this.factory.create('text') as TextNode;
  736. text.setText(options['fullErrors'] ? message : this.kind);
  737. mtext.appendChild(text);
  738. merror.appendChild(mtext);
  739. this.parent.replaceChild(merror, this);
  740. } else {
  741. this.parent.replaceChild(merror, this);
  742. merror.appendChild(this);
  743. }
  744. return merror;
  745. }
  746. }
  747. /*****************************************************************/
  748. /**
  749. * The abstract MmlNode Token node class (extends the AbstractMmlNode)
  750. */
  751. export abstract class AbstractMmlTokenNode extends AbstractMmlNode {
  752. /**
  753. * Add the attributes common to all token nodes
  754. */
  755. public static defaults: PropertyList = {
  756. ...AbstractMmlNode.defaults,
  757. mathvariant: 'normal',
  758. mathsize: INHERIT
  759. };
  760. /**
  761. * @override
  762. */
  763. public get isToken() {
  764. return true;
  765. }
  766. /**
  767. * Get the text of the token node (skipping mglyphs, and combining
  768. * multiple text nodes)
  769. */
  770. public getText() {
  771. let text = '';
  772. for (const child of this.childNodes) {
  773. if (child instanceof TextNode) {
  774. text += child.getText();
  775. }
  776. }
  777. return text;
  778. }
  779. /**
  780. * Only inherit to child nodes that are AbstractMmlNodes (not TextNodes)
  781. *
  782. * @override
  783. */
  784. protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
  785. for (const child of this.childNodes) {
  786. if (child instanceof AbstractMmlNode) {
  787. child.setInheritedAttributes(attributes, display, level, prime);
  788. }
  789. }
  790. }
  791. /**
  792. * Only step into children that are AbstractMmlNodes (not TextNodes)
  793. * @override
  794. */
  795. public walkTree(func: (node: Node, data?: any) => void, data?: any) {
  796. func(this, data);
  797. for (const child of this.childNodes) {
  798. if (child instanceof AbstractMmlNode) {
  799. child.walkTree(func, data);
  800. }
  801. }
  802. return data;
  803. }
  804. }
  805. /*****************************************************************/
  806. /**
  807. * The abstract MmlNode Layout class (extends the AbstractMmlNode)
  808. *
  809. * These have inferred mrows (so only one child) and can be
  810. * spacelike or embellished based on their contents.
  811. */
  812. export abstract class AbstractMmlLayoutNode extends AbstractMmlNode {
  813. /**
  814. * Use the same defaults as AbstractMmlNodes
  815. */
  816. public static defaults: PropertyList = AbstractMmlNode.defaults;
  817. /**
  818. * @override
  819. */
  820. public get isSpacelike() {
  821. return this.childNodes[0].isSpacelike;
  822. }
  823. /**
  824. * @override
  825. */
  826. public get isEmbellished() {
  827. return this.childNodes[0].isEmbellished;
  828. }
  829. /**
  830. * @override
  831. */
  832. public get arity() {
  833. return -1;
  834. }
  835. /**
  836. * @override
  837. */
  838. public core() {
  839. return this.childNodes[0];
  840. }
  841. /**
  842. * @override
  843. */
  844. public coreMO() {
  845. return this.childNodes[0].coreMO();
  846. }
  847. /**
  848. * @override
  849. */
  850. public setTeXclass(prev: MmlNode) {
  851. prev = this.childNodes[0].setTeXclass(prev);
  852. this.updateTeXclass(this.childNodes[0]);
  853. return prev;
  854. }
  855. }
  856. /*****************************************************************/
  857. /**
  858. * The abstract MmlNode-with-base-node Class (extends the AbstractMmlNode)
  859. *
  860. * These have a base element and other elemetns, (e.g., script elements for msubsup).
  861. * They can be embellished (if their base is), and get their TeX classes
  862. * from their base with their scripts being handled as separate math lists.
  863. */
  864. export abstract class AbstractMmlBaseNode extends AbstractMmlNode {
  865. /**
  866. * Use the same defaults as AbstractMmlNodes
  867. */
  868. public static defaults: PropertyList = AbstractMmlNode.defaults;
  869. /**
  870. * @override
  871. */
  872. public get isEmbellished() {
  873. return this.childNodes[0].isEmbellished;
  874. }
  875. /**
  876. * @override
  877. */
  878. public core() {
  879. return this.childNodes[0];
  880. }
  881. /**
  882. * @override
  883. */
  884. public coreMO() {
  885. return this.childNodes[0].coreMO();
  886. }
  887. /**
  888. * @override
  889. */
  890. public setTeXclass(prev: MmlNode) {
  891. this.getPrevClass(prev);
  892. this.texClass = TEXCLASS.ORD;
  893. let base = this.childNodes[0];
  894. if (base) {
  895. if (this.isEmbellished || base.isKind('mi')) {
  896. prev = base.setTeXclass(prev);
  897. this.updateTeXclass(this.core());
  898. } else {
  899. base.setTeXclass(null);
  900. prev = this;
  901. }
  902. } else {
  903. prev = this;
  904. }
  905. for (const child of this.childNodes.slice(1)) {
  906. if (child) {
  907. child.setTeXclass(null);
  908. }
  909. }
  910. return prev;
  911. }
  912. }
  913. /*****************************************************************/
  914. /**
  915. * The abstract MmlNode Empty Class (extends AbstractEmptyNode, implements MmlNode)
  916. *
  917. * These have no children and no attributes (TextNode and XMLNode), so we
  918. * override all the methods dealing with them, and with the data that usually
  919. * goes with an MmlNode.
  920. */
  921. export abstract class AbstractMmlEmptyNode extends AbstractEmptyNode implements MmlNode {
  922. /**
  923. * Parent is an MmlNode
  924. */
  925. public parent: MmlNode;
  926. /**
  927. * @return {boolean} Not a token element
  928. */
  929. public get isToken(): boolean {
  930. return false;
  931. }
  932. /**
  933. * @return {boolean} Not embellished
  934. */
  935. public get isEmbellished(): boolean {
  936. return false;
  937. }
  938. /**
  939. * @return {boolean} Not space-like
  940. */
  941. public get isSpacelike(): boolean {
  942. return false;
  943. }
  944. /**
  945. * @return {boolean} Not a container of any kind
  946. */
  947. public get linebreakContainer(): boolean {
  948. return false;
  949. }
  950. /**
  951. * @return {boolean} Does not contain new lines
  952. */
  953. public get hasNewLine(): boolean {
  954. return false;
  955. }
  956. /**
  957. * @return {number} No children
  958. */
  959. public get arity(): number {
  960. return 0;
  961. }
  962. /**
  963. * @return {boolean} Is not an inferred row
  964. */
  965. public get isInferred(): boolean {
  966. return false;
  967. }
  968. /**
  969. * @return {boolean} Is not a container element
  970. */
  971. public get notParent(): boolean {
  972. return false;
  973. }
  974. /**
  975. * @return {MmlNode} Parent is the actual parent
  976. */
  977. public get Parent(): MmlNode {
  978. return this.parent;
  979. }
  980. /**
  981. * @return {number} No TeX class
  982. */
  983. public get texClass(): number {
  984. return TEXCLASS.NONE;
  985. }
  986. /**
  987. * @return {number} No previous element
  988. */
  989. public get prevClass(): number {
  990. return TEXCLASS.NONE;
  991. }
  992. /**
  993. * @return {number} No previous element
  994. */
  995. public get prevLevel(): number {
  996. return 0;
  997. }
  998. /**
  999. * @return {boolean} The core mo element has an explicit 'form' attribute
  1000. */
  1001. public hasSpacingAttributes(): boolean {
  1002. return false;
  1003. }
  1004. /**
  1005. * return {Attributes} No attributes, so don't store one
  1006. */
  1007. public get attributes(): Attributes {
  1008. return null;
  1009. }
  1010. /**
  1011. * @override
  1012. */
  1013. public core(): MmlNode {
  1014. return this;
  1015. }
  1016. /**
  1017. * @override
  1018. */
  1019. public coreMO(): MmlNode {
  1020. return this;
  1021. }
  1022. /**
  1023. * @override
  1024. */
  1025. public coreIndex() {
  1026. return 0;
  1027. }
  1028. /**
  1029. * @override
  1030. */
  1031. public childPosition() {
  1032. return 0;
  1033. }
  1034. /**
  1035. * @override
  1036. */
  1037. public setTeXclass(prev: MmlNode) {
  1038. return prev;
  1039. }
  1040. /**
  1041. * @override
  1042. */
  1043. public texSpacing() {
  1044. return '';
  1045. }
  1046. /**
  1047. * No children or attributes, so ignore this call.
  1048. *
  1049. * @override
  1050. */
  1051. public setInheritedAttributes(_attributes: AttributeList, _display: boolean, _level: number, _prime: boolean) {}
  1052. /**
  1053. * No children or attributes, so ignore this call.
  1054. *
  1055. * @override
  1056. */
  1057. public inheritAttributesFrom(_node: MmlNode) {}
  1058. /**
  1059. * No children or attributes, so ignore this call.
  1060. *
  1061. * @param {PropertyList} options The options for the check
  1062. */
  1063. public verifyTree(_options: PropertyList) {}
  1064. /**
  1065. * @override
  1066. */
  1067. public mError(_message: string, _options: PropertyList, _short: boolean = false) {
  1068. return null as MmlNode;
  1069. }
  1070. }
  1071. /*****************************************************************/
  1072. /**
  1073. * The TextNode Class (extends AbstractMmlEmptyNode)
  1074. */
  1075. export class TextNode extends AbstractMmlEmptyNode {
  1076. /**
  1077. * The text for this node
  1078. */
  1079. protected text: string = '';
  1080. /**
  1081. * @override
  1082. */
  1083. public get kind() {
  1084. return 'text';
  1085. }
  1086. /**
  1087. * @return {string} Return the node's text
  1088. */
  1089. public getText(): string {
  1090. return this.text;
  1091. }
  1092. /**
  1093. * @param {string} text The text to use for the node
  1094. * @return {TextNode} The text node (for chaining of method calls)
  1095. */
  1096. public setText(text: string): TextNode {
  1097. this.text = text;
  1098. return this;
  1099. }
  1100. /**
  1101. * @override
  1102. */
  1103. public copy() {
  1104. return (this.factory.create(this.kind) as TextNode).setText(this.getText());
  1105. }
  1106. /**
  1107. * Just use the text
  1108. */
  1109. public toString() {
  1110. return this.text;
  1111. }
  1112. }
  1113. /*****************************************************************/
  1114. /**
  1115. * The XMLNode Class (extends AbstractMmlEmptyNode)
  1116. */
  1117. export class XMLNode extends AbstractMmlEmptyNode {
  1118. /**
  1119. * The XML content for this node
  1120. */
  1121. protected xml: Object = null;
  1122. /**
  1123. * DOM adaptor for the content
  1124. */
  1125. protected adaptor: DOMAdaptor<any, any, any> = null;
  1126. /**
  1127. * @override
  1128. */
  1129. public get kind() {
  1130. return 'XML';
  1131. }
  1132. /**
  1133. * @return {Object} Return the node's XML content
  1134. */
  1135. public getXML(): Object {
  1136. return this.xml;
  1137. }
  1138. /**
  1139. * @param {object} xml The XML content to be saved
  1140. * @param {DOMAdaptor} adaptor DOM adaptor for the content
  1141. * @return {XMLNode} The XML node (for chaining of method calls)
  1142. */
  1143. public setXML(xml: Object, adaptor: DOMAdaptor<any, any, any> = null): XMLNode {
  1144. this.xml = xml;
  1145. this.adaptor = adaptor;
  1146. return this;
  1147. }
  1148. /**
  1149. * @return {string} The serialized XML content
  1150. */
  1151. public getSerializedXML(): string {
  1152. return this.adaptor.serializeXML(this.xml);
  1153. }
  1154. /**
  1155. * @override
  1156. */
  1157. public copy(): XMLNode {
  1158. return (this.factory.create(this.kind) as XMLNode).setXML(this.adaptor.clone(this.xml));
  1159. }
  1160. /**
  1161. * Just indicate that this is XML data
  1162. */
  1163. public toString() {
  1164. return 'XML data';
  1165. }
  1166. }