123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242 |
- /*************************************************************
- *
- * Copyright (c) 2009-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 Stack items for basic Tex parsing.
- *
- * @author v.sorge@mathjax.org (Volker Sorge)
- */
- import {MapHandler} from '../MapHandler.js';
- import {CharacterMap} from '../SymbolMap.js';
- import {entities} from '../../../util/Entities.js';
- import {MmlNode, TextNode, TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
- import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
- import TexError from '../TexError.js';
- import ParseUtil from '../ParseUtil.js';
- import NodeUtil from '../NodeUtil.js';
- import {Property} from '../../../core/Tree/Node.js';
- import StackItemFactory from '../StackItemFactory.js';
- import {CheckType, BaseItem, StackItem, EnvList} from '../StackItem.js';
- /**
- * Initial item on the stack. It's pushed when parsing begins.
- */
- export class StartItem extends BaseItem {
- /**
- * @override
- */
- constructor(factory: StackItemFactory, public global: EnvList) {
- super(factory);
- }
- /**
- * @override
- */
- public get kind() {
- return 'start';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('stop')) {
- let node = this.toMml();
- if (!this.global.isInner) {
- node = this.factory.configuration.tags.finalize(node, this.env);
- }
- return [[this.factory.create('mml', node)], true];
- }
- return super.checkItem(item);
- }
- }
- /**
- * Final item on the stack. Errors will be thrown if other items than the start
- * item are still on the stack.
- */
- export class StopItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'stop';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Item indicating an open brace.
- */
- export class OpenItem extends BaseItem {
- /**
- * @override
- */
- protected static errors = Object.assign(Object.create(BaseItem.errors), {
- // @test ExtraOpenMissingClose
- 'stop': ['ExtraOpenMissingClose',
- 'Extra open brace or missing close brace']
- });
- /**
- * @override
- */
- public get kind() {
- return 'open';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('close')) {
- // @test PrimeSup
- let mml = this.toMml();
- const node = this.create('node', 'TeXAtom', [mml]);
- return [[this.factory.create('mml', node)], true];
- }
- return super.checkItem(item);
- }
- }
- /**
- * Item indicating a close brace. Collapses stack until an OpenItem is found.
- */
- export class CloseItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'close';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Item indicating an we are currently dealing with a prime mark.
- */
- export class PrimeItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'prime';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- let [top0, top1] = this.Peek(2);
- if (!NodeUtil.isType(top0, 'msubsup') || NodeUtil.isType(top0, 'msup')) {
- // @test Prime, Double Prime
- const node = this.create('node', 'msup', [top0, top1]);
- return [[node, item], true];
- }
- NodeUtil.setChild(top0, (top0 as MmlMsubsup).sup, top1);
- return [[top0, item], true];
- }
- }
- /**
- * Item indicating an we are currently dealing with a sub/superscript
- * expression.
- */
- export class SubsupItem extends BaseItem {
- /**
- * @override
- */
- protected static errors = Object.assign(Object.create(BaseItem.errors), {
- // @test MissingScript Sub, MissingScript Sup
- 'stop': ['MissingScript',
- 'Missing superscript or subscript argument'],
- // @test MissingOpenForSup
- 'sup': ['MissingOpenForSup',
- 'Missing open brace for superscript'],
- // @test MissingOpenForSub
- 'sub': ['MissingOpenForSub',
- 'Missing open brace for subscript']
- });
- /**
- * @override
- */
- public get kind() {
- return 'subsup';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType | null {
- if (item.isKind('open') || item.isKind('left')) {
- return BaseItem.success;
- }
- const top = this.First;
- const position = this.getProperty('position') as number;
- if (item.isKind('mml')) {
- if (this.getProperty('primes')) {
- if (position !== 2) {
- // @test Prime on Sub
- NodeUtil.setChild(top, 2, this.getProperty('primes') as MmlNode);
- } else {
- // @test Prime on Prime
- NodeUtil.setProperty(this.getProperty('primes') as MmlNode, 'variantForm', true);
- const node = this.create('node', 'mrow', [this.getProperty('primes') as MmlNode, item.First]);
- item.First = node;
- }
- }
- NodeUtil.setChild(top, position, item.First);
- if (this.getProperty('movesupsub') != null) {
- // @test Limits Subsup (currently does not work! Check again!)
- NodeUtil.setProperty(top, 'movesupsub', this.getProperty('movesupsub') as Property);
- }
- const result = this.factory.create('mml', top);
- return [[result], true];
- }
- if (super.checkItem(item)[1]) {
- // @test Brace Superscript Error, MissingOpenForSup, MissingOpenForSub
- const error = this.getErrors(['', 'sub', 'sup'][position]);
- throw new TexError(error[0], error[1], ...error.splice(2));
- }
- return null;
- }
- }
- /**
- * Item indicating an we are currently dealing with an \\over command.
- */
- export class OverItem extends BaseItem {
- /**
- * @override
- */
- constructor(factory: StackItemFactory) {
- super(factory);
- this.setProperty('name', '\\over');
- }
- /**
- * @override
- */
- public get kind() {
- return 'over';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('over')) {
- // @test Double Over
- throw new TexError(
- 'AmbiguousUseOf', 'Ambiguous use of %1', item.getName());
- }
- if (item.isClose) {
- // @test Over
- let mml = this.create('node',
- 'mfrac', [this.getProperty('num') as MmlNode, this.toMml(false)]);
- if (this.getProperty('thickness') != null) {
- // @test Choose, Above, Above with Delims
- NodeUtil.setAttribute(mml, 'linethickness',
- this.getProperty('thickness') as string);
- }
- if (this.getProperty('open') || this.getProperty('close')) {
- // @test Choose
- NodeUtil.setProperty(mml, 'withDelims', true);
- mml = ParseUtil.fixedFence(this.factory.configuration,
- this.getProperty('open') as string, mml,
- this.getProperty('close') as string);
- }
- return [[this.factory.create('mml', mml), item], true];
- }
- return super.checkItem(item);
- }
- /**
- * @override
- */
- public toString() {
- return 'over[' + this.getProperty('num') +
- ' / ' + this.nodes.join('; ') + ']';
- }
- }
- /**
- * Item pushed when a \\left opening delimiter has been found.
- */
- export class LeftItem extends BaseItem {
- /**
- * @override
- */
- protected static errors = Object.assign(Object.create(BaseItem.errors), {
- // @test ExtraLeftMissingRight
- 'stop': ['ExtraLeftMissingRight',
- 'Extra \\left or missing \\right']
- });
- /**
- * @override
- */
- constructor(factory: StackItemFactory, delim: string) {
- super(factory);
- this.setProperty('delim', delim);
- }
- /**
- * @override
- */
- public get kind() {
- return 'left';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- // @test Missing Right
- if (item.isKind('right')) {
- //
- // Create the fenced structure as an mrow
- //
- return [[this.factory.create('mml', ParseUtil.fenced(
- this.factory.configuration,
- this.getProperty('delim') as string, this.toMml(),
- item.getProperty('delim') as string, '', item.getProperty('color') as string))], true];
- }
- if (item.isKind('middle')) {
- //
- // Add the middle delimiter, with empty open and close elements around it for spacing
- //
- const def = {stretchy: true} as any;
- if (item.getProperty('color')) {
- def.mathcolor = item.getProperty('color');
- }
- this.Push(
- this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.CLOSE}),
- this.create('token', 'mo', def, item.getProperty('delim')),
- this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.OPEN})
- );
- this.env = {}; // Since \middle closes the group, clear the environment
- return [[this], true]; // this will reset the environment to its initial state
- }
- return super.checkItem(item);
- }
- }
- /**
- * Item pushed when a \\middle delimiter has been found. Stack is
- * collapsed until a corresponding LeftItem is encountered.
- */
- export class Middle extends BaseItem {
- /**
- * @override
- */
- constructor(factory: StackItemFactory, delim: string, color: string) {
- super(factory);
- this.setProperty('delim', delim);
- color && this.setProperty('color', color);
- }
- /**
- * @override
- */
- public get kind() {
- return 'middle';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Item pushed when a \\right closing delimiter has been found. Stack is
- * collapsed until a corresponding LeftItem is encountered.
- */
- export class RightItem extends BaseItem {
- /**
- * @override
- */
- constructor(factory: StackItemFactory, delim: string, color: string) {
- super(factory);
- this.setProperty('delim', delim);
- color && this.setProperty('color', color);
- }
- /**
- * @override
- */
- public get kind() {
- return 'right';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Item pushed for opening an environment with \\begin{env}.
- */
- export class BeginItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'begin';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('end')) {
- if (item.getName() !== this.getName()) {
- // @test EnvBadEnd
- throw new TexError('EnvBadEnd', '\\begin{%1} ended with \\end{%2}',
- this.getName(), item.getName());
- }
- if (!this.getProperty('end')) {
- // @test Hfill
- return [[this.factory.create('mml', this.toMml())], true];
- }
- return BaseItem.fail; // TODO: This case could probably go!
- }
- if (item.isKind('stop')) {
- // @test EnvMissingEnd Array
- throw new TexError('EnvMissingEnd', 'Missing \\end{%1}', this.getName());
- }
- return super.checkItem(item);
- }
- }
- /**
- * Item pushed for closing an environment with \\end{env}. Stack is collapsed
- * until a corresponding BeginItem for 'env' is found. Error is thrown in case
- * other open environments interfere.
- */
- export class EndItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'end';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Item pushed for remembering styling information.
- */
- export class StyleItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'style';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (!item.isClose) {
- return super.checkItem(item);
- }
- // @test Style
- const mml = this.create('node', 'mstyle', this.nodes, this.getProperty('styles'));
- return [[this.factory.create('mml', mml), item], true];
- }
- }
- /**
- * Item pushed for remembering positioning information.
- */
- export class PositionItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'position';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isClose) {
- // @test MissingBoxFor
- throw new TexError('MissingBoxFor', 'Missing box for %1', this.getName());
- }
- if (item.isFinal) {
- let mml = item.toMml();
- switch (this.getProperty('move')) {
- case 'vertical':
- // @test Raise, Lower, Raise Negative, Lower Negative
- mml = this.create('node', 'mpadded', [mml],
- {height: this.getProperty('dh'),
- depth: this.getProperty('dd'),
- voffset: this.getProperty('dh')});
- return [[this.factory.create('mml', mml)], true];
- case 'horizontal':
- // @test Move Left, Move Right, Move Left Negative, Move Right Negative
- return [[this.factory.create('mml', this.getProperty('left') as MmlNode), item,
- this.factory.create('mml', this.getProperty('right') as MmlNode)], true];
- }
- }
- return super.checkItem(item);
- }
- }
- /**
- * Item indicating a table cell.
- */
- export class CellItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'cell';
- }
- /**
- * @override
- */
- get isClose() {
- return true;
- }
- }
- /**
- * Final item for collating Nodes.
- */
- export class MmlItem extends BaseItem {
- /**
- * @override
- */
- public get isFinal() {
- return true;
- }
- /**
- * @override
- */
- public get kind() {
- return 'mml';
- }
- }
- /**
- * Item indicating a named function operator (e.g., \\sin) as been encountered.
- */
- export class FnItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'fn';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- const top = this.First;
- if (top) {
- if (item.isOpen) {
- // @test Fn Stretchy
- return BaseItem.success;
- }
- if (!item.isKind('fn')) {
- // @test Named Function
- let mml = item.First;
- if (!item.isKind('mml') || !mml) {
- // @test Mathop Super
- return [[top, item], true];
- }
- if ((NodeUtil.isType(mml, 'mstyle') && mml.childNodes.length &&
- NodeUtil.isType(mml.childNodes[0].childNodes[0] as MmlNode, 'mspace')) ||
- NodeUtil.isType(mml, 'mspace')) {
- // @test Fn Pos Space, Fn Neg Space
- return [[top, item], true];
- }
- if (NodeUtil.isEmbellished(mml)) {
- // @test MultiInt with Limits
- mml = NodeUtil.getCoreMO(mml);
- }
- const form = NodeUtil.getForm(mml);
- if (form != null && [0, 0, 1, 1, 0, 1, 1, 0, 0, 0][form[2]]) {
- // @test Fn Operator
- return [[top, item], true];
- }
- }
- // @test Named Function, Named Function Arg
- const node = this.create('token', 'mo', {texClass: TEXCLASS.NONE},
- entities.ApplyFunction);
- return [[top, node, item], true];
- }
- // @test Mathop Super, Mathop Sub
- return super.checkItem.apply(this, arguments);
- }
- }
- /**
- * Item indicating a \\not has been encountered and needs to be applied to the
- * next operator.
- */
- export class NotItem extends BaseItem {
- private remap = MapHandler.getMap('not_remap') as CharacterMap;
- /**
- * @override
- */
- public get kind() {
- return 'not';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- let mml: TextNode | MmlNode;
- let c: string;
- let textNode: TextNode;
- if (item.isKind('open') || item.isKind('left')) {
- // @test Negation Left Paren
- return BaseItem.success;
- }
- if (item.isKind('mml') &&
- (NodeUtil.isType(item.First, 'mo') || NodeUtil.isType(item.First, 'mi') ||
- NodeUtil.isType(item.First, 'mtext'))) {
- mml = item.First;
- c = NodeUtil.getText(mml as TextNode);
- if (c.length === 1 && !NodeUtil.getProperty(mml, 'movesupsub') &&
- NodeUtil.getChildren(mml).length === 1) {
- if (this.remap.contains(c)) {
- // @test Negation Simple, Negation Complex
- textNode = this.create('text', this.remap.lookup(c).char) as TextNode;
- NodeUtil.setChild(mml, 0, textNode);
- } else {
- // @test Negation Explicit
- textNode = this.create('text', '\u0338') as TextNode;
- NodeUtil.appendChildren(mml, [textNode]);
- }
- return [[item], true];
- }
- }
- // @test Negation Large
- textNode = this.create('text', '\u29F8') as TextNode;
- const mtextNode = this.create('node', 'mtext', [], {}, textNode);
- const paddedNode = this.create('node', 'mpadded', [mtextNode], {width: 0});
- mml = this.create('node', 'TeXAtom', [paddedNode], {texClass: TEXCLASS.REL});
- return [[mml, item], true];
- }
- }
- /**
- * A StackItem that removes an mspace that follows it (for \nonscript).
- */
- export class NonscriptItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'nonscript';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- //
- // Check if the next item is an mspace (or an mspace in an mstyle) and remove it.
- //
- if (item.isKind('mml') && item.Size() === 1) {
- let mml = item.First;
- //
- // Space macros like \, are wrapped with an mstyle to set scriptlevel="0"
- // (so size is independent of level), we look at the contents of the mstyle for the mspace.
- //
- if (mml.isKind('mstyle') && mml.notParent) {
- mml = NodeUtil.getChildren(NodeUtil.getChildren(mml)[0])[0];
- }
- if (mml.isKind('mspace')) {
- //
- // If the space is in an mstyle, wrap it in an mrow so we can test its scriptlevel
- // in the post-filter (the mrow will be removed in the filter). We can't test
- // the mstyle's scriptlevel, since it is ecxplicitly setting it to 0.
- //
- if (mml !== item.First) {
- const mrow = this.create('node', 'mrow', [item.Pop()]);
- item.Push(mrow);
- }
- //
- // Save the mspace for later post-processing.
- //
- this.factory.configuration.addNode('nonscript', item.First);
- }
- }
- return [[item], true];
- }
- }
- /**
- * Item indicating a dots command has been encountered.
- */
- export class DotsItem extends BaseItem {
- /**
- * @override
- */
- public get kind() {
- return 'dots';
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('open') || item.isKind('left')) {
- return BaseItem.success;
- }
- let dots = this.getProperty('ldots') as MmlNode;
- let top = item.First;
- // @test Operator Dots
- if (item.isKind('mml') && NodeUtil.isEmbellished(top)) {
- const tclass = NodeUtil.getTexClass(NodeUtil.getCoreMO(top));
- if (tclass === TEXCLASS.BIN || tclass === TEXCLASS.REL) {
- dots = this.getProperty('cdots') as MmlNode;
- }
- }
- return [[dots, item], true];
- }
- }
- /**
- * Item indicating an array is assembled. It collates cells, rows and
- * information about column/row separator and framing lines.
- */
- export class ArrayItem extends BaseItem {
- /**
- * The table as a list of rows.
- * @type {MmlNode[]}
- */
- public table: MmlNode[] = [];
- /**
- * The current row as a list of cells.
- * @type {MmlNode[]}
- */
- public row: MmlNode[] = [];
- /**
- * Frame specification as a list of strings.
- * @type {string[]}
- */
- public frame: string[] = [];
- /**
- * Hfill value.
- * @type {number[]}
- */
- public hfill: number[] = [];
- /**
- * Properties for special array definitions.
- * @type {{[key: string]: string|number|boolean}}
- */
- public arraydef: {[key: string]: string | number | boolean} = {};
- /**
- * True if separators are dashed.
- * @type {boolean}
- */
- public dashed: boolean = false;
- /**
- * @override
- */
- public get kind() {
- return 'array';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- get copyEnv() {
- return false;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- // @test Array Single
- if (item.isClose && !item.isKind('over')) {
- // @test Array Single
- if (item.getProperty('isEntry')) {
- // @test Array dashed column, Array solid column
- this.EndEntry();
- this.clearEnv();
- return BaseItem.fail;
- }
- if (item.getProperty('isCR')) {
- // @test Enclosed bottom
- this.EndEntry();
- this.EndRow();
- this.clearEnv();
- return BaseItem.fail;
- }
- this.EndTable();
- this.clearEnv();
- let newItem = this.factory.create('mml', this.createMml());
- if (this.getProperty('requireClose')) {
- // @test: Label
- if (item.isKind('close')) {
- // @test: Label
- return [[newItem], true];
- }
- // @test MissingCloseBrace2
- throw new TexError('MissingCloseBrace', 'Missing close brace');
- }
- return [[newItem, item], true];
- }
- return super.checkItem(item);
- }
- /**
- * Create the MathML representation of the table.
- *
- * @return {MmlNode}
- */
- public createMml(): MmlNode {
- const scriptlevel = this.arraydef['scriptlevel'];
- delete this.arraydef['scriptlevel'];
- let mml = this.create('node', 'mtable', this.table, this.arraydef);
- if (scriptlevel) {
- mml.setProperty('scriptlevel', scriptlevel);
- }
- if (this.frame.length === 4) {
- // @test Enclosed frame solid, Enclosed frame dashed
- NodeUtil.setAttribute(mml, 'frame', this.dashed ? 'dashed' : 'solid');
- } else if (this.frame.length) {
- // @test Enclosed left right
- if (this.arraydef['rowlines']) {
- // @test Enclosed dashed row, Enclosed solid row,
- this.arraydef['rowlines'] =
- (this.arraydef['rowlines'] as string).replace(/none( none)+$/, 'none');
- }
- // @test Enclosed left right
- NodeUtil.setAttribute(mml, 'frame', '');
- mml = this.create('node', 'menclose', [mml], {notation: this.frame.join(' ')});
- if ((this.arraydef['columnlines'] || 'none') !== 'none' ||
- (this.arraydef['rowlines'] || 'none') !== 'none') {
- // @test Enclosed dashed row, Enclosed solid row
- // @test Enclosed dashed column, Enclosed solid column
- NodeUtil.setAttribute(mml, 'data-padding', 0);
- }
- }
- if (this.getProperty('open') || this.getProperty('close')) {
- // @test Cross Product Formula
- mml = ParseUtil.fenced(this.factory.configuration,
- this.getProperty('open') as string, mml,
- this.getProperty('close') as string);
- }
- return mml;
- }
- /**
- * Finishes a single cell of the array.
- */
- public EndEntry() {
- // @test Array1, Array2
- const mtd = this.create('node', 'mtd', this.nodes);
- if (this.hfill.length) {
- if (this.hfill[0] === 0) {
- NodeUtil.setAttribute(mtd, 'columnalign', 'right');
- }
- if (this.hfill[this.hfill.length - 1] === this.Size()) {
- NodeUtil.setAttribute(
- mtd, 'columnalign',
- NodeUtil.getAttribute(mtd, 'columnalign') ? 'center' : 'left');
- }
- }
- this.row.push(mtd);
- this.Clear();
- this.hfill = [];
- }
- /**
- * Finishes a single row of the array.
- */
- public EndRow() {
- let node: MmlNode;
- if (this.getProperty('isNumbered') && this.row.length === 3) {
- // @test Label, Matrix Numbered
- this.row.unshift(this.row.pop()); // move equation number to first
- // position
- node = this.create('node', 'mlabeledtr', this.row);
- } else {
- // @test Array1, Array2
- node = this.create('node', 'mtr', this.row);
- }
- this.table.push(node);
- this.row = [];
- }
- /**
- * Finishes the table layout.
- */
- public EndTable() {
- if (this.Size() || this.row.length) {
- this.EndEntry();
- this.EndRow();
- }
- this.checkLines();
- }
- /**
- * Finishes line layout if not already given.
- */
- public checkLines() {
- if (this.arraydef['rowlines']) {
- const lines = (this.arraydef['rowlines'] as string).split(/ /);
- if (lines.length === this.table.length) {
- this.frame.push('bottom');
- lines.pop();
- this.arraydef['rowlines'] = lines.join(' ');
- } else if (lines.length < this.table.length - 1) {
- this.arraydef['rowlines'] += ' none';
- }
- }
- if (this.getProperty('rowspacing')) {
- const rows = (this.arraydef['rowspacing'] as string).split(/ /);
- while (rows.length < this.table.length) {
- rows.push(this.getProperty('rowspacing') + 'em');
- }
- this.arraydef['rowspacing'] = rows.join(' ');
- }
- }
- /**
- * Adds a row-spacing to the current row (padding out the rowspacing if needed to get there).
- *
- * @param {string} spacing The rowspacing to use for the current row.
- */
- public addRowSpacing(spacing: string) {
- if (this.arraydef['rowspacing']) {
- const rows = (this.arraydef['rowspacing'] as string).split(/ /);
- if (!this.getProperty('rowspacing')) {
- // @test Array Custom Linebreak
- let dimem = ParseUtil.dimen2em(rows[0]);
- this.setProperty('rowspacing', dimem);
- }
- const rowspacing = this.getProperty('rowspacing') as number;
- while (rows.length < this.table.length) {
- rows.push(ParseUtil.Em(rowspacing));
- }
- rows[this.table.length - 1] = ParseUtil.Em(
- Math.max(0, rowspacing + ParseUtil.dimen2em(spacing)));
- this.arraydef['rowspacing'] = rows.join(' ');
- }
- }
- }
- /**
- * Item dealing with equation arrays as a special case of arrays. Handles
- * tagging information according to the given tagging style.
- */
- export class EqnArrayItem extends ArrayItem {
- /**
- * The length of the longest row.
- */
- public maxrow: number = 0;
- /**
- * @override
- */
- constructor(factory: any, ...args: any[]) {
- super(factory);
- this.factory.configuration.tags.start(args[0], args[2], args[1]);
- }
- /**
- * @override
- */
- get kind() {
- return 'eqnarray';
- }
- /**
- * @override
- */
- public EndEntry() {
- // @test Cubic Binomial
- if (this.row.length) {
- ParseUtil.fixInitialMO(this.factory.configuration, this.nodes);
- }
- const node = this.create('node', 'mtd', this.nodes);
- this.row.push(node);
- this.Clear();
- }
- /**
- * @override
- */
- public EndRow() {
- if (this.row.length > this.maxrow) {
- this.maxrow = this.row.length;
- }
- // @test Cubic Binomial
- let mtr = 'mtr';
- let tag = this.factory.configuration.tags.getTag();
- if (tag) {
- this.row = [tag].concat(this.row);
- mtr = 'mlabeledtr';
- }
- this.factory.configuration.tags.clearTag();
- const node = this.create('node', mtr, this.row);
- this.table.push(node);
- this.row = [];
- }
- /**
- * @override
- */
- public EndTable() {
- // @test Cubic Binomial
- super.EndTable();
- this.factory.configuration.tags.end();
- //
- // Repeat the column align and width specifications
- // to match the number of columns
- //
- this.extendArray('columnalign', this.maxrow);
- this.extendArray('columnwidth', this.maxrow);
- this.extendArray('columnspacing', this.maxrow - 1);
- }
- /**
- * Extend a column specification to include a repeating set of values
- * so that it has enough to match the maximum row length.
- */
- protected extendArray(name: string, max: number) {
- if (!this.arraydef[name]) return;
- const repeat = (this.arraydef[name] as string).split(/ /);
- const columns = [...repeat];
- if (columns.length > 1) {
- while (columns.length < max) {
- columns.push(...repeat);
- }
- this.arraydef[name] = columns.slice(0, max).join(' ');
- }
- }
- }
- /**
- * Item dealing with simple equation environments. Handles tagging information
- * according to the given tagging style.
- */
- export class EquationItem extends BaseItem {
- /**
- * @override
- */
- constructor(factory: any, ...args: any[]) {
- super(factory);
- this.factory.configuration.tags.start('equation', true, args[0]);
- }
- /**
- * @override
- */
- get kind() {
- return 'equation';
- }
- /**
- * @override
- */
- get isOpen() {
- return true;
- }
- /**
- * @override
- */
- public checkItem(item: StackItem): CheckType {
- if (item.isKind('end')) {
- let mml = this.toMml();
- let tag = this.factory.configuration.tags.getTag();
- this.factory.configuration.tags.end();
- return [[tag ? this.factory.configuration.tags.enTag(mml, tag) : mml, item], true];
- }
- if (item.isKind('stop')) {
- // @test EnvMissingEnd Equation
- throw new TexError('EnvMissingEnd', 'Missing \\end{%1}', this.getName());
- }
- return super.checkItem(item);
- }
- }
|