123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314 |
- /*************************************************************
- *
- * Copyright (c) 2017-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Interfaces and abstract classes for MmlNode objects
- *
- * @author dpvc@mathjax.org (Davide Cervone)
- */
- import {Attributes, INHERIT} from './Attributes.js';
- import {Property, PropertyList, Node, AbstractNode, AbstractEmptyNode, NodeClass} from '../Tree/Node.js';
- import {MmlFactory} from './MmlFactory.js';
- import {DOMAdaptor} from '../DOMAdaptor.js';
- /**
- * Used in setInheritedAttributes() to pass originating node kind as well as property value
- */
- export type AttributeList = {[attribute: string]: [string, Property]};
- /**
- * These are the TeX classes for spacing computations
- */
- export const TEXCLASS = {
- ORD: 0,
- OP: 1,
- BIN: 2,
- REL: 3,
- OPEN: 4,
- CLOSE: 5,
- PUNCT: 6,
- INNER: 7,
- VCENTER: 8, // Used in TeXAtom, but not for spacing
- NONE: -1
- };
- export const TEXCLASSNAMES = ['ORD', 'OP', 'BIN', 'REL', 'OPEN', 'CLOSE', 'PUNCT', 'INNER', 'VCENTER'];
- /**
- * The spacing sizes used by the TeX spacing table below.
- */
- const TEXSPACELENGTH = ['', 'thinmathspace', 'mediummathspace', 'thickmathspace'];
- /**
- * See TeXBook Chapter 18 (p. 170)
- */
- const TEXSPACE = [
- [ 0, -1, 2, 3, 0, 0, 0, 1], // ORD
- [-1, -1, 0, 3, 0, 0, 0, 1], // OP
- [ 2, 2, 0, 0, 2, 0, 0, 2], // BIN
- [ 3, 3, 0, 0, 3, 0, 0, 3], // REL
- [ 0, 0, 0, 0, 0, 0, 0, 0], // OPEN
- [ 0, -1, 2, 3, 0, 0, 0, 1], // CLOSE
- [ 1, 1, 0, 1, 1, 1, 1, 1], // PUNCT
- [ 1, -1, 2, 3, 1, 0, 1, 1] // INNER
- ];
- /**
- * Attributes used to determine indentation and shifting
- */
- export const indentAttributes = [
- 'indentalign', 'indentalignfirst',
- 'indentshift', 'indentshiftfirst'
- ];
- /**
- * The nodes that can be in the internal MathML tree
- */
- export type MMLNODE = MmlNode | TextNode | XMLNode;
- /*****************************************************************/
- /**
- * The MmlNode interface (extends Node interface)
- */
- export interface MmlNode extends Node {
- /**
- * Test various properties of MathML nodes
- */
- readonly isToken: boolean;
- readonly isEmbellished: boolean;
- readonly isSpacelike: boolean;
- readonly linebreakContainer: boolean;
- readonly hasNewLine: boolean;
- /**
- * The expected number of children (-1 means use inferred mrow)
- */
- readonly arity: number;
- readonly isInferred: boolean;
- /**
- * Get the parent node (skipping inferred mrows and
- * other nodes marked as notParent)
- */
- readonly Parent: MmlNode;
- readonly notParent: boolean;
- /**
- * The actual parent in the tree
- */
- parent: MmlNode;
- /**
- * values needed for TeX spacing computations
- */
- texClass: number;
- prevClass: number;
- prevLevel: number;
- /**
- * The attributes (explicit and inherited) for this node
- */
- attributes: Attributes;
- /**
- * @return {MmlNode} For embellished operators, the child node that contains the
- * core <mo> node. For non-embellished nodes, the original node.
- */
- core(): MmlNode;
- /**
- * @return {MmlNode} For embellished operators, the core <mo> element (at whatever
- * depth). For non-embellished nodes, the original node itself.
- */
- coreMO(): MmlNode;
- /**
- * @return {number} For embellished operators, the index of the child node containing
- * the core <mo>. For non-embellished nodes, 0.
- */
- coreIndex(): number;
- /**
- * @return {number} The index of this node in its parent's childNodes array.
- */
- childPosition(): number;
- /**
- * @param {MmlNode} prev The node that is before this one for TeX spacing purposes
- * (not all nodes count in TeX measurements)
- * @return {MmlNode} The node that should be the previous node for the next one
- * in the tree (usually, either the last child, or the node itself)
- */
- setTeXclass(prev: MmlNode): MmlNode;
- /**
- * @return {string} The spacing to use before this element (one of TEXSPACELENGTH array above)
- */
- texSpacing(): string;
- /**
- * @return {boolean} The core mo element has an explicit 'form', 'lspace', or 'rspace' attribute
- */
- hasSpacingAttributes(): boolean;
- /**
- * Sets the nodes inherited attributes, and pushes them to the nodes children.
- *
- * @param {AttributeList} attributes The list of inheritable attributes (with the node kinds
- * from which they came)
- * @param {boolean} display The displaystyle to inherit
- * @param {number} level The scriptlevel to inherit
- * @param {boolean} prime The TeX prime style to inherit (T vs. T', etc).
- */
- setInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean): void;
- /**
- * Set the nodes inherited attributes based on the attributes of the given node
- * (used for creating extra nodes in the tree after setInheritedAttributes has already run)
- *
- * @param {MmlNode} node The node whose attributes are to be used as a template
- */
- inheritAttributesFrom(node: MmlNode): void;
- /**
- * Replace the current node with an error message (or the name of the node)
- *
- * @param {string} message The error message to use
- * @param {PropertyList} options The options telling how much to verify
- * @param {boolean} short True means use just the kind if not using full errors
- * @return {MmlNode} The construted merror
- */
- mError(message: string, options: PropertyList, short?: boolean): MmlNode;
- /**
- * Check integrity of MathML structure
- *
- * @param {PropertyList} options The options controlling the check
- */
- verifyTree(options?: PropertyList): void;
- }
- /*****************************************************************/
- /**
- * The MmlNode class interface (extends the NodeClass)
- */
- export interface MmlNodeClass extends NodeClass {
- /**
- * The list of default attribute values for nodes of this class
- */
- defaults?: PropertyList;
- /**
- * An MmlNode takes a NodeFactory (so it can create additional nodes as needed), a list
- * of attributes, and an array of children and returns the desired MmlNode with
- * those attributes and children
- *
- * @constructor
- * @param {MmlFactory} factory The MathML node factory to use to create additional nodes
- * @param {PropertyList} attributes The list of initial attributes for the node
- * @param {MmlNode[]} children The initial child nodes (more can be added later)
- */
- new (factory: MmlFactory, attributes?: PropertyList, children?: MmlNode[]): MmlNode;
- }
- /*****************************************************************/
- /**
- * The abstract MmlNode class (extends the AbstractNode class and implements
- * the IMmlNode interface)
- */
- export abstract class AbstractMmlNode extends AbstractNode implements MmlNode {
- /**
- * The properties common to all MathML nodes
- */
- public static defaults: PropertyList = {
- mathbackground: INHERIT,
- mathcolor: INHERIT,
- mathsize: INHERIT, // technically only for token elements, but <mstyle mathsize="..."> should
- // scale all spaces, fractions, etc.
- dir: INHERIT
- };
- /**
- * This lists properties that do NOT get inherited between specific kinds
- * of nodes. The outer keys are the node kinds that are being inherited FROM,
- * while the second level of keys are the nodes that INHERIT the values. Any
- * property appearing in the innermost list is NOT inherited by the pair.
- *
- * For example, an mpadded element will not inherit a width attribute from an mstyle node.
- */
- public static noInherit: {[node1: string]: {[node2: string]: {[attribute: string]: boolean}}} = {
- mstyle: {
- mpadded: {width: true, height: true, depth: true, lspace: true, voffset: true},
- mtable: {width: true, height: true, depth: true, align: true}
- },
- maligngroup: {
- mrow: {groupalign: true},
- mtable: {groupalign: true}
- }
- };
- /**
- * This lists the attributes that should always be inherited,
- * even when there is no default value for the attribute.
- */
- public static alwaysInherit: {[name: string]: boolean} = {
- scriptminsize: true,
- scriptsizemultiplier: true
- };
- /**
- * This is the list of options for the verifyTree() method
- */
- public static verifyDefaults: PropertyList = {
- checkArity: true,
- checkAttributes: false,
- fullErrors: false,
- fixMmultiscripts: true,
- fixMtables: true
- };
- /*
- * These default to being unset (the node doesn't participate in spacing calculations).
- * The correct values are produced when the setTeXclass() method is called on the tree.
- */
- /**
- * The TeX class for the preceding node
- */
- public prevClass: number = null;
- /**
- * The scriptlevel of the preceding node
- */
- public prevLevel: number = null;
- /**
- * This node's attributes
- */
- public attributes: Attributes;
- /**
- * Child nodes are MmlNodes (special case of Nodes).
- */
- public childNodes: MmlNode[];
- /**
- * The parent is an MmlNode
- */
- public parent: MmlNode;
- /**
- * The node factory is an MmlFactory
- */
- public readonly factory: MmlFactory;
- /**
- * The TeX class of this node (obtained via texClass below)
- */
- protected texclass: number = null;
- /**
- * Create an MmlNode:
- * If the arity is -1, add the inferred row (created by the factory)
- * Add the children, if any
- * Create the Attribute object from the class defaults and the global defaults (the math node defaults)
- *
- * @override
- */
- constructor(factory: MmlFactory, attributes: PropertyList = {}, children: MmlNode[] = []) {
- super(factory);
- if (this.arity < 0) {
- this.childNodes = [factory.create('inferredMrow')];
- this.childNodes[0].parent = this;
- }
- this.setChildren(children);
- this.attributes = new Attributes(
- factory.getNodeClass(this.kind).defaults,
- factory.getNodeClass('math').defaults
- );
- this.attributes.setList(attributes);
- }
- /**
- * @override
- *
- * @param {boolean} keepIds True to copy id attributes, false to skip them.
- * (May cause error in the future, since not part of the interface.)
- * @return {AbstractMmlNode} The copied node tree.
- */
- public copy(keepIds: boolean = false): AbstractMmlNode {
- const node = this.factory.create(this.kind) as AbstractMmlNode;
- node.properties = {...this.properties};
- if (this.attributes) {
- const attributes = this.attributes.getAllAttributes();
- for (const name of Object.keys(attributes)) {
- if (name !== 'id' || keepIds) {
- node.attributes.set(name, attributes[name]);
- }
- }
- }
- if (this.childNodes && this.childNodes.length) {
- let children = this.childNodes as MmlNode[];
- if (children.length === 1 && children[0].isInferred) {
- children = children[0].childNodes as MmlNode[];
- }
- for (const child of children) {
- if (child) {
- node.appendChild(child.copy() as MmlNode);
- } else {
- node.childNodes.push(null);
- }
- }
- }
- return node;
- }
- /**
- * The TeX class for this node
- */
- public get texClass(): number {
- return this.texclass;
- }
- /**
- * The TeX class for this node
- */
- public set texClass(texClass: number) {
- this.texclass = texClass;
- }
- /**
- * @return {boolean} true if this is a token node
- */
- public get isToken(): boolean {
- return false;
- }
- /**
- * @return {boolean} true if this is an embellished operator
- */
- public get isEmbellished(): boolean {
- return false;
- }
- /**
- * @return {boolean} true if this is a space-like node
- */
- public get isSpacelike(): boolean {
- return false;
- }
- /**
- * @return {boolean} true if this is a node that supports linebreaks in its children
- */
- public get linebreakContainer(): boolean {
- return false;
- }
- /**
- * @return {boolean} true if this node contains a line break
- */
- public get hasNewLine(): boolean {
- return false;
- }
- /**
- * @return {number} The number of children allowed, or Infinity for any number,
- * or -1 for when an inferred row is needed for the children.
- * Special case is 1, meaning at least one (other numbers
- * mean exactly that many).
- */
- public get arity(): number {
- return Infinity;
- }
- /**
- * @return {boolean} true if this is an inferred mrow
- */
- public get isInferred(): boolean {
- return false;
- }
- /**
- * @return {MmlNode} The logical parent of this node (skipping over inferred rows
- * some other node types)
- */
- public get Parent(): MmlNode {
- let parent = this.parent;
- while (parent && parent.notParent) {
- parent = parent.Parent;
- }
- return parent;
- }
- /**
- * @return {boolean} true if this is a node that doesn't count as a parent node in Parent()
- */
- public get notParent(): boolean {
- return false;
- }
- /**
- * If there is an inferred row, the the children of that instead
- *
- * @override
- */
- public setChildren(children: MmlNode[]) {
- if (this.arity < 0) {
- return this.childNodes[0].setChildren(children);
- }
- return super.setChildren(children);
- }
- /**
- * If there is an inferred row, append to that instead.
- * If a child is inferred, append its children instead.
- *
- * @override
- */
- public appendChild(child: MmlNode) {
- if (this.arity < 0) {
- this.childNodes[0].appendChild(child);
- return child;
- }
- if (child.isInferred) {
- //
- // If we can have arbitrary children, remove the inferred mrow
- // (just add its children).
- //
- if (this.arity === Infinity) {
- child.childNodes.forEach((node) => super.appendChild(node));
- return child;
- }
- //
- // Otherwise, convert the inferred mrow to an explicit mrow
- //
- const original = child;
- child = this.factory.create('mrow');
- child.setChildren(original.childNodes);
- child.attributes = original.attributes;
- for (const name of original.getPropertyNames()) {
- child.setProperty(name, original.getProperty(name));
- }
- }
- return super.appendChild(child);
- }
- /**
- * If there is an inferred row, remove the child from there
- *
- * @override
- */
- public replaceChild(newChild: MmlNode, oldChild: MmlNode) {
- if (this.arity < 0) {
- this.childNodes[0].replaceChild(newChild, oldChild);
- return newChild;
- }
- return super.replaceChild(newChild, oldChild);
- }
- /**
- * @override
- */
- public core(): MmlNode {
- return this;
- }
- /**
- * @override
- */
- public coreMO(): MmlNode {
- return this;
- }
- /**
- * @override
- */
- public coreIndex() {
- return 0;
- }
- /**
- * @override
- */
- public childPosition() {
- let child: MmlNode = this;
- let parent = child.parent;
- while (parent && parent.notParent) {
- child = parent;
- parent = parent.parent;
- }
- if (parent) {
- let i = 0;
- for (const node of parent.childNodes) {
- if (node === child) {
- return i;
- }
- i++;
- }
- }
- return null;
- }
- /**
- * @override
- */
- public setTeXclass(prev: MmlNode): MmlNode {
- this.getPrevClass(prev);
- return (this.texClass != null ? this : prev);
- }
- /**
- * For embellished operators, get the data from the core and clear the core
- *
- * @param {MmlNode} core The core <mo> for this node
- */
- protected updateTeXclass(core: MmlNode) {
- if (core) {
- this.prevClass = core.prevClass;
- this.prevLevel = core.prevLevel;
- core.prevClass = core.prevLevel = null;
- this.texClass = core.texClass;
- }
- }
- /**
- * Get the previous element's texClass and scriptlevel
- *
- * @param {MmlNode} prev The previous node to this one
- */
- protected getPrevClass(prev: MmlNode) {
- if (prev) {
- this.prevClass = prev.texClass;
- this.prevLevel = prev.attributes.get('scriptlevel') as number;
- }
- }
- /**
- * @return {string} returns the spacing to use before this node
- */
- public texSpacing(): string {
- let prevClass = (this.prevClass != null ? this.prevClass : TEXCLASS.NONE);
- let texClass = this.texClass || TEXCLASS.ORD;
- if (prevClass === TEXCLASS.NONE || texClass === TEXCLASS.NONE) {
- return '';
- }
- if (prevClass === TEXCLASS.VCENTER) {
- prevClass = TEXCLASS.ORD;
- }
- if (texClass === TEXCLASS.VCENTER) {
- texClass = TEXCLASS.ORD;
- }
- let space = TEXSPACE[prevClass][texClass];
- if ((this.prevLevel > 0 || this.attributes.get('scriptlevel') > 0) && space >= 0) {
- return '';
- }
- return TEXSPACELENGTH[Math.abs(space)];
- }
- /**
- * @return {boolean} The core mo element has an explicit 'form' attribute
- */
- public hasSpacingAttributes(): boolean {
- return this.isEmbellished && this.coreMO().hasSpacingAttributes();
- }
- /**
- * Sets the inherited propertis for this node, and pushes inherited properties to the children
- *
- * For each inheritable attribute:
- * If the node has a default for this attribute, try to inherit it
- * but check if the noInherit object prevents that.
- * If the node doesn't have an explicit displaystyle, inherit it
- * If the node doesn't have an explicit scriptstyle, inherit it
- * If the prime style is true, set it as a property (it is not a MathML attribute)
- * Check that the number of children is correct
- * Finally, push any inherited attributes to teh children.
- *
- * @override
- */
- public setInheritedAttributes(attributes: AttributeList = {},
- display: boolean = false, level: number = 0, prime: boolean = false) {
- let defaults = this.attributes.getAllDefaults();
- for (const key of Object.keys(attributes)) {
- if (defaults.hasOwnProperty(key) || AbstractMmlNode.alwaysInherit.hasOwnProperty(key)) {
- let [node, value] = attributes[key];
- let noinherit = (AbstractMmlNode.noInherit[node] || {})[this.kind] || {};
- if (!noinherit[key]) {
- this.attributes.setInherited(key, value);
- }
- }
- }
- let displaystyle = this.attributes.getExplicit('displaystyle');
- if (displaystyle === undefined) {
- this.attributes.setInherited('displaystyle', display);
- }
- let scriptlevel = this.attributes.getExplicit('scriptlevel');
- if (scriptlevel === undefined) {
- this.attributes.setInherited('scriptlevel', level);
- }
- if (prime) {
- this.setProperty('texprimestyle', prime);
- }
- let arity = this.arity;
- if (arity >= 0 && arity !== Infinity && ((arity === 1 && this.childNodes.length === 0) ||
- (arity !== 1 && this.childNodes.length !== arity))) {
- //
- // Make sure there are the right number of child nodes
- // (trim them or add empty mrows)
- //
- if (arity < this.childNodes.length) {
- this.childNodes = this.childNodes.slice(0, arity);
- } else {
- while (this.childNodes.length < arity) {
- this.appendChild(this.factory.create('mrow'));
- }
- }
- }
- this.setChildInheritedAttributes(attributes, display, level, prime);
- }
- /**
- * Apply inherited attributes to all children
- * (Some classes override this to handle changes in displaystyle and scriptlevel)
- *
- * @param {AttributeList} attributes The list of inheritable attributes (with the node kinds
- * from which they came)
- * @param {boolean} display The displaystyle to inherit
- * @param {number} level The scriptlevel to inherit
- * @param {boolean} prime The TeX prime style to inherit (T vs. T', etc).
- */
- protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
- for (const child of this.childNodes) {
- child.setInheritedAttributes(attributes, display, level, prime);
- }
- }
- /**
- * Used by subclasses to add their own attributes to the inherited list
- * (e.g., mstyle uses this to augment the inherited attibutes)
- *
- * @param {AttributeList} current The current list of inherited attributes
- * @param {PropertyList} attributes The new attributes to add into the list
- */
- protected addInheritedAttributes(current: AttributeList, attributes: PropertyList) {
- let updated: AttributeList = {...current};
- for (const name of Object.keys(attributes)) {
- if (name !== 'displaystyle' && name !== 'scriptlevel' && name !== 'style') {
- updated[name] = [this.kind, attributes[name]];
- }
- }
- return updated;
- }
- /**
- * Set the nodes inherited attributes based on the attributes of the given node
- * (used for creating extra nodes in the tree after setInheritedAttributes has already run)
- *
- * @param {MmlNode} node The node whose attributes are to be used as a template
- */
- public inheritAttributesFrom(node: MmlNode) {
- const attributes = node.attributes;
- const display = attributes.get('displaystyle') as boolean;
- const scriptlevel = attributes.get('scriptlevel') as number;
- const defaults: AttributeList = (!attributes.isSet('mathsize') ? {} : {
- mathsize: ['math', attributes.get('mathsize')]
- });
- const prime = node.getProperty('texprimestyle') as boolean || false;
- this.setInheritedAttributes(defaults, display, scriptlevel, prime);
- }
- /**
- * Verify the attributes, and that there are the right number of children.
- * Then verify the children.
- *
- * @param {PropertyList} options The options telling how much to verify
- */
- public verifyTree(options: PropertyList = null) {
- if (options === null) {
- return;
- }
- this.verifyAttributes(options);
- let arity = this.arity;
- if (options['checkArity']) {
- if (arity >= 0 && arity !== Infinity &&
- ((arity === 1 && this.childNodes.length === 0) ||
- (arity !== 1 && this.childNodes.length !== arity))) {
- this.mError('Wrong number of children for "' + this.kind + '" node', options, true);
- }
- }
- this.verifyChildren(options);
- }
- /**
- * Verify that all the attributes are valid (i.e., have defaults)
- *
- * @param {PropertyList} options The options telling how much to verify
- */
- protected verifyAttributes(options: PropertyList) {
- if (options['checkAttributes']) {
- const attributes = this.attributes;
- const bad = [];
- for (const name of attributes.getExplicitNames()) {
- if (name.substr(0, 5) !== 'data-' && attributes.getDefault(name) === undefined &&
- !name.match(/^(?:class|style|id|(?:xlink:)?href)$/)) {
- // FIXME: provide a configurable checker for names that are OK
- bad.push(name);
- }
- // FIXME: add ability to check attribute values?
- }
- if (bad.length) {
- this.mError('Unknown attributes for ' + this.kind + ' node: ' + bad.join(', '), options);
- }
- }
- }
- /**
- * Verify the children.
- *
- * @param {PropertyList} options The options telling how much to verify
- */
- protected verifyChildren(options: PropertyList) {
- for (const child of this.childNodes) {
- child.verifyTree(options);
- }
- }
- /**
- * Replace the current node with an error message (or the name of the node)
- *
- * @param {string} message The error message to use
- * @param {PropertyList} options The options telling how much to verify
- * @param {boolean} short True means use just the kind if not using full errors
- * @return {MmlNode} The constructed merror
- */
- public mError(message: string, options: PropertyList, short: boolean = false): MmlNode {
- if (this.parent && this.parent.isKind('merror')) {
- return null;
- }
- let merror = this.factory.create('merror');
- merror.attributes.set('data-mjx-message', message);
- if (options['fullErrors'] || short) {
- let mtext = this.factory.create('mtext');
- let text = this.factory.create('text') as TextNode;
- text.setText(options['fullErrors'] ? message : this.kind);
- mtext.appendChild(text);
- merror.appendChild(mtext);
- this.parent.replaceChild(merror, this);
- } else {
- this.parent.replaceChild(merror, this);
- merror.appendChild(this);
- }
- return merror;
- }
- }
- /*****************************************************************/
- /**
- * The abstract MmlNode Token node class (extends the AbstractMmlNode)
- */
- export abstract class AbstractMmlTokenNode extends AbstractMmlNode {
- /**
- * Add the attributes common to all token nodes
- */
- public static defaults: PropertyList = {
- ...AbstractMmlNode.defaults,
- mathvariant: 'normal',
- mathsize: INHERIT
- };
- /**
- * @override
- */
- public get isToken() {
- return true;
- }
- /**
- * Get the text of the token node (skipping mglyphs, and combining
- * multiple text nodes)
- */
- public getText() {
- let text = '';
- for (const child of this.childNodes) {
- if (child instanceof TextNode) {
- text += child.getText();
- }
- }
- return text;
- }
- /**
- * Only inherit to child nodes that are AbstractMmlNodes (not TextNodes)
- *
- * @override
- */
- protected setChildInheritedAttributes(attributes: AttributeList, display: boolean, level: number, prime: boolean) {
- for (const child of this.childNodes) {
- if (child instanceof AbstractMmlNode) {
- child.setInheritedAttributes(attributes, display, level, prime);
- }
- }
- }
- /**
- * Only step into children that are AbstractMmlNodes (not TextNodes)
- * @override
- */
- public walkTree(func: (node: Node, data?: any) => void, data?: any) {
- func(this, data);
- for (const child of this.childNodes) {
- if (child instanceof AbstractMmlNode) {
- child.walkTree(func, data);
- }
- }
- return data;
- }
- }
- /*****************************************************************/
- /**
- * The abstract MmlNode Layout class (extends the AbstractMmlNode)
- *
- * These have inferred mrows (so only one child) and can be
- * spacelike or embellished based on their contents.
- */
- export abstract class AbstractMmlLayoutNode extends AbstractMmlNode {
- /**
- * Use the same defaults as AbstractMmlNodes
- */
- public static defaults: PropertyList = AbstractMmlNode.defaults;
- /**
- * @override
- */
- public get isSpacelike() {
- return this.childNodes[0].isSpacelike;
- }
- /**
- * @override
- */
- public get isEmbellished() {
- return this.childNodes[0].isEmbellished;
- }
- /**
- * @override
- */
- public get arity() {
- return -1;
- }
- /**
- * @override
- */
- public core() {
- return this.childNodes[0];
- }
- /**
- * @override
- */
- public coreMO() {
- return this.childNodes[0].coreMO();
- }
- /**
- * @override
- */
- public setTeXclass(prev: MmlNode) {
- prev = this.childNodes[0].setTeXclass(prev);
- this.updateTeXclass(this.childNodes[0]);
- return prev;
- }
- }
- /*****************************************************************/
- /**
- * The abstract MmlNode-with-base-node Class (extends the AbstractMmlNode)
- *
- * These have a base element and other elemetns, (e.g., script elements for msubsup).
- * They can be embellished (if their base is), and get their TeX classes
- * from their base with their scripts being handled as separate math lists.
- */
- export abstract class AbstractMmlBaseNode extends AbstractMmlNode {
- /**
- * Use the same defaults as AbstractMmlNodes
- */
- public static defaults: PropertyList = AbstractMmlNode.defaults;
- /**
- * @override
- */
- public get isEmbellished() {
- return this.childNodes[0].isEmbellished;
- }
- /**
- * @override
- */
- public core() {
- return this.childNodes[0];
- }
- /**
- * @override
- */
- public coreMO() {
- return this.childNodes[0].coreMO();
- }
- /**
- * @override
- */
- public setTeXclass(prev: MmlNode) {
- this.getPrevClass(prev);
- this.texClass = TEXCLASS.ORD;
- let base = this.childNodes[0];
- if (base) {
- if (this.isEmbellished || base.isKind('mi')) {
- prev = base.setTeXclass(prev);
- this.updateTeXclass(this.core());
- } else {
- base.setTeXclass(null);
- prev = this;
- }
- } else {
- prev = this;
- }
- for (const child of this.childNodes.slice(1)) {
- if (child) {
- child.setTeXclass(null);
- }
- }
- return prev;
- }
- }
- /*****************************************************************/
- /**
- * The abstract MmlNode Empty Class (extends AbstractEmptyNode, implements MmlNode)
- *
- * These have no children and no attributes (TextNode and XMLNode), so we
- * override all the methods dealing with them, and with the data that usually
- * goes with an MmlNode.
- */
- export abstract class AbstractMmlEmptyNode extends AbstractEmptyNode implements MmlNode {
- /**
- * Parent is an MmlNode
- */
- public parent: MmlNode;
- /**
- * @return {boolean} Not a token element
- */
- public get isToken(): boolean {
- return false;
- }
- /**
- * @return {boolean} Not embellished
- */
- public get isEmbellished(): boolean {
- return false;
- }
- /**
- * @return {boolean} Not space-like
- */
- public get isSpacelike(): boolean {
- return false;
- }
- /**
- * @return {boolean} Not a container of any kind
- */
- public get linebreakContainer(): boolean {
- return false;
- }
- /**
- * @return {boolean} Does not contain new lines
- */
- public get hasNewLine(): boolean {
- return false;
- }
- /**
- * @return {number} No children
- */
- public get arity(): number {
- return 0;
- }
- /**
- * @return {boolean} Is not an inferred row
- */
- public get isInferred(): boolean {
- return false;
- }
- /**
- * @return {boolean} Is not a container element
- */
- public get notParent(): boolean {
- return false;
- }
- /**
- * @return {MmlNode} Parent is the actual parent
- */
- public get Parent(): MmlNode {
- return this.parent;
- }
- /**
- * @return {number} No TeX class
- */
- public get texClass(): number {
- return TEXCLASS.NONE;
- }
- /**
- * @return {number} No previous element
- */
- public get prevClass(): number {
- return TEXCLASS.NONE;
- }
- /**
- * @return {number} No previous element
- */
- public get prevLevel(): number {
- return 0;
- }
- /**
- * @return {boolean} The core mo element has an explicit 'form' attribute
- */
- public hasSpacingAttributes(): boolean {
- return false;
- }
- /**
- * return {Attributes} No attributes, so don't store one
- */
- public get attributes(): Attributes {
- return null;
- }
- /**
- * @override
- */
- public core(): MmlNode {
- return this;
- }
- /**
- * @override
- */
- public coreMO(): MmlNode {
- return this;
- }
- /**
- * @override
- */
- public coreIndex() {
- return 0;
- }
- /**
- * @override
- */
- public childPosition() {
- return 0;
- }
- /**
- * @override
- */
- public setTeXclass(prev: MmlNode) {
- return prev;
- }
- /**
- * @override
- */
- public texSpacing() {
- return '';
- }
- /**
- * No children or attributes, so ignore this call.
- *
- * @override
- */
- public setInheritedAttributes(_attributes: AttributeList, _display: boolean, _level: number, _prime: boolean) {}
- /**
- * No children or attributes, so ignore this call.
- *
- * @override
- */
- public inheritAttributesFrom(_node: MmlNode) {}
- /**
- * No children or attributes, so ignore this call.
- *
- * @param {PropertyList} options The options for the check
- */
- public verifyTree(_options: PropertyList) {}
- /**
- * @override
- */
- public mError(_message: string, _options: PropertyList, _short: boolean = false) {
- return null as MmlNode;
- }
- }
- /*****************************************************************/
- /**
- * The TextNode Class (extends AbstractMmlEmptyNode)
- */
- export class TextNode extends AbstractMmlEmptyNode {
- /**
- * The text for this node
- */
- protected text: string = '';
- /**
- * @override
- */
- public get kind() {
- return 'text';
- }
- /**
- * @return {string} Return the node's text
- */
- public getText(): string {
- return this.text;
- }
- /**
- * @param {string} text The text to use for the node
- * @return {TextNode} The text node (for chaining of method calls)
- */
- public setText(text: string): TextNode {
- this.text = text;
- return this;
- }
- /**
- * @override
- */
- public copy() {
- return (this.factory.create(this.kind) as TextNode).setText(this.getText());
- }
- /**
- * Just use the text
- */
- public toString() {
- return this.text;
- }
- }
- /*****************************************************************/
- /**
- * The XMLNode Class (extends AbstractMmlEmptyNode)
- */
- export class XMLNode extends AbstractMmlEmptyNode {
- /**
- * The XML content for this node
- */
- protected xml: Object = null;
- /**
- * DOM adaptor for the content
- */
- protected adaptor: DOMAdaptor<any, any, any> = null;
- /**
- * @override
- */
- public get kind() {
- return 'XML';
- }
- /**
- * @return {Object} Return the node's XML content
- */
- public getXML(): Object {
- return this.xml;
- }
- /**
- * @param {object} xml The XML content to be saved
- * @param {DOMAdaptor} adaptor DOM adaptor for the content
- * @return {XMLNode} The XML node (for chaining of method calls)
- */
- public setXML(xml: Object, adaptor: DOMAdaptor<any, any, any> = null): XMLNode {
- this.xml = xml;
- this.adaptor = adaptor;
- return this;
- }
- /**
- * @return {string} The serialized XML content
- */
- public getSerializedXML(): string {
- return this.adaptor.serializeXML(this.xml);
- }
- /**
- * @override
- */
- public copy(): XMLNode {
- return (this.factory.create(this.kind) as XMLNode).setXML(this.adaptor.clone(this.xml));
- }
- /**
- * Just indicate that this is XML data
- */
- public toString() {
- return 'XML data';
- }
- }
|