From 5dc3abedcb2af85372653342c4995307453dd508 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 14 Jun 2026 19:28:44 -0400 Subject: [PATCH 1/4] Ignore braces in array preamble. (mathjax/MathJax#3565) --- testsuite/tests/input/tex/Base.test.ts | 4 ++++ .../tests/input/tex/__snapshots__/Base.test.ts.snap | 12 ++++++++++++ ts/input/tex/ColumnParser.ts | 2 ++ 3 files changed, 18 insertions(+) diff --git a/testsuite/tests/input/tex/Base.test.ts b/testsuite/tests/input/tex/Base.test.ts index 6e06df5d7..0052be20f 100644 --- a/testsuite/tests/input/tex/Base.test.ts +++ b/testsuite/tests/input/tex/Base.test.ts @@ -4002,6 +4002,10 @@ describe('Complete Array', () => { expect(tex2mml('\\begin{array}{> {x} c} X \\end{array}')).toMatchSnapshot(); }); + it('column { }', () => { + expect(tex2mml('\\begin{array}{{c}} X \\end{array}')).toMatchSnapshot(); + }); + it('BadPreamToken', () => { expectTexError('\\begin{array}a').toBe('Illegal pream-token (a)'); }); diff --git a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap index bc59e9aa3..db75f586c 100644 --- a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap +++ b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap @@ -1872,6 +1872,18 @@ exports[`Complete Array column @ p m 1`] = ` " `; +exports[`Complete Array column { } 1`] = ` +" + + + + X + + + +" +`; + exports[`Complete Array column b 1`] = ` " diff --git a/ts/input/tex/ColumnParser.ts b/ts/input/tex/ColumnParser.ts index a0beec563..e4c8a7133 100644 --- a/ts/input/tex/ColumnParser.ts +++ b/ts/input/tex/ColumnParser.ts @@ -94,6 +94,8 @@ export class ColumnParser { // Ignored // ' ': (_state) => {}, + '{': (_state) => {}, + '}': (_state) => {}, }; /** From 3142fe43a7d76a0aa0eca3b05879315ef2fb193a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 18 Jun 2026 19:21:19 -0400 Subject: [PATCH 2/4] Update handling of braces to be more like LaTeX --- testsuite/tests/input/tex/Base.test.ts | 18 ++++++++- .../input/tex/__snapshots__/Base.test.ts.snap | 34 +++++++++++++++-- ts/input/tex/ColumnParser.ts | 38 ++++++++++++++++--- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/testsuite/tests/input/tex/Base.test.ts b/testsuite/tests/input/tex/Base.test.ts index 0052be20f..f51892f58 100644 --- a/testsuite/tests/input/tex/Base.test.ts +++ b/testsuite/tests/input/tex/Base.test.ts @@ -4002,8 +4002,22 @@ describe('Complete Array', () => { expect(tex2mml('\\begin{array}{> {x} c} X \\end{array}')).toMatchSnapshot(); }); - it('column { }', () => { - expect(tex2mml('\\begin{array}{{c}} X \\end{array}')).toMatchSnapshot(); + it('column {cc}', () => { + expect(tex2mml('\\begin{array}{{cc}} X Y \\end{array}')).toMatchSnapshot(); + }); + + it('column {c}', () => { + expect(tex2mml('\\begin{array}{c{c}} X Y \\end{array}')).toMatchSnapshot(); + }); + + it('column {{c}}', () => { + expect(tex2mml('\\begin{array}{{{c}}} X \\end{array}')).toMatchSnapshot(); + }); + + it('column brace errors', () => { + expectTexError(tex2mml('\\begin{array}{c{xx}} X Y \\end{array}')).toBe('Illegal pream-token (xx)'); + expectTexError(tex2mml('\\begin{array}{c{{c}}} X Y \\end{array}')).toBe('Illegal pream-token ({c})'); + expectTexError(tex2mml('\\begin{array}{c{c{c}}} X Y \\end{array}')).toBe('Illegal pream-token (c{c})'); }); it('BadPreamToken', () => { diff --git a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap index db75f586c..75eb4d1d9 100644 --- a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap +++ b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap @@ -1872,10 +1872,10 @@ exports[`Complete Array column @ p m 1`] = ` " `; -exports[`Complete Array column { } 1`] = ` -" - - +exports[`Complete Array column {{c}} 1`] = ` +" + + X @@ -1884,6 +1884,32 @@ exports[`Complete Array column { } 1`] = ` " `; +exports[`Complete Array column {c} 1`] = ` +" + + + + X + Y + + + +" +`; + +exports[`Complete Array column {cc} 1`] = ` +" + + + + X + Y + + + +" +`; + exports[`Complete Array column b 1`] = ` " diff --git a/ts/input/tex/ColumnParser.ts b/ts/input/tex/ColumnParser.ts index e4c8a7133..f8d7ef260 100644 --- a/ts/input/tex/ColumnParser.ts +++ b/ts/input/tex/ColumnParser.ts @@ -84,6 +84,7 @@ export class ColumnParser { '@': (state) => this.addAt(state, this.getBraces(state)), '!': (state) => this.addBang(state, this.getBraces(state)), '*': (state) => this.repeat(state), + '{': (state) => this.brace(state), // // Non-standard for math-mode versions // @@ -94,8 +95,7 @@ export class ColumnParser { // Ignored // ' ': (_state) => {}, - '{': (_state) => {}, - '}': (_state) => {}, + '\n': (_state_) => {}, }; /** @@ -111,6 +111,12 @@ export class ColumnParser { * @param {ArrayItem} array The ArrayItem for the template */ public process(parser: TexParser, template: string, array: ArrayItem) { + // + // Remove one pair of braces, if there are any + // + if (template.charAt(0) === '{' && template.slice(-1) === '}') { + template = template.slice(1, -1); + } // // Initialize the state // @@ -143,10 +149,7 @@ export class ColumnParser { const code = state.template.codePointAt(state.i); const c = (state.c = String.fromCodePoint(code)); state.i += c.length; - if (!Object.hasOwn(this.columnHandler, c)) { - throw new TexError('BadPreamToken', 'Illegal pream-token (%1)', c); - } - this.columnHandler[c](state); + this.processColumn(state, c); } // // Set array definition values @@ -158,6 +161,19 @@ export class ColumnParser { this.setPadding(state, array); } + /** + * Process a column specifier, or throw an error if not defined. + * + * @param {ColumnState} state The current state of the parser + * @param {string} c The column type to process + */ + protected processColumn(state: ColumnState, c: string) { + if (!Object.hasOwn(this.columnHandler, c)) { + throw new TexError('BadPreamToken', 'Illegal pream-token (%1)', c); + } + this.columnHandler[c](state); + } + /** * @param {ColumnState} state The current state of the parser * @param {ArrayItem} array The array stack item to adjust @@ -422,4 +438,14 @@ export class ColumnParser { new Array(n).fill(cols).join('') + state.template.substring(state.i); state.i = 0; } + + /** + * Process a braced column type + * + * @param {ColumnState} state The current state of the parser + */ + public brace(state: ColumnState) { + state.i--; + this.processColumn(state, this.getBraces(state)); + } } From 40eb098171bd6ea247cb85cf7d2292777c58e08a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 18 Jun 2026 21:22:40 -0400 Subject: [PATCH 3/4] Get order of repeated >{} or <{} directives correct and add tests --- testsuite/tests/input/tex/Base.test.ts | 8 +++++ .../input/tex/__snapshots__/Base.test.ts.snap | 34 +++++++++++++++++++ ts/input/tex/ColumnParser.ts | 4 +-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/testsuite/tests/input/tex/Base.test.ts b/testsuite/tests/input/tex/Base.test.ts index f51892f58..f31218918 100644 --- a/testsuite/tests/input/tex/Base.test.ts +++ b/testsuite/tests/input/tex/Base.test.ts @@ -4014,6 +4014,14 @@ describe('Complete Array', () => { expect(tex2mml('\\begin{array}{{{c}}} X \\end{array}')).toMatchSnapshot(); }); + it('column newline', () => { + expect(tex2mml('\\begin{array}{c\nc} X & Y\\end{array}')).toMatchSnapshot(); + }); + + it('column > > c < <', () => { + expect(tex2mml('\\begin{array}{>{a}>{b}c<{x}<{y}} X \\end{array}')).toMatchSnapshot(); + }); + it('column brace errors', () => { expectTexError(tex2mml('\\begin{array}{c{xx}} X Y \\end{array}')).toBe('Illegal pream-token (xx)'); expectTexError(tex2mml('\\begin{array}{c{{c}}} X Y \\end{array}')).toBe('Illegal pream-token ({c})'); diff --git a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap index 75eb4d1d9..745260a02 100644 --- a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap +++ b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap @@ -1820,6 +1820,22 @@ exports[`Complete Array column ! | @ 1`] = ` " `; +exports[`Complete Array column > > c < < 1`] = ` +" + + + + b + a + X + y + x + + + +" +`; + exports[`Complete Array column > space 1`] = ` " @@ -2217,6 +2233,24 @@ exports[`Complete Array column m 1`] = ` " `; +exports[`Complete Array column newline 1`] = ` +" + + + + X + + + Y + + + +" +`; + exports[`Complete Array column p 1`] = ` " diff --git a/ts/input/tex/ColumnParser.ts b/ts/input/tex/ColumnParser.ts index f8d7ef260..cc41977e4 100644 --- a/ts/input/tex/ColumnParser.ts +++ b/ts/input/tex/ColumnParser.ts @@ -77,10 +77,10 @@ export class ColumnParser { ':': (state) => this.addRule(state, 'dashed'), '>': (state) => (state.cstart[state.j] = - (state.cstart[state.j] || '') + this.getBraces(state)), + this.getBraces(state) + (state.cstart[state.j] || '')), '<': (state) => (state.cend[state.j - 1] = - (state.cend[state.j - 1] || '') + this.getBraces(state)), + this.getBraces(state) + (state.cend[state.j - 1] || '')), '@': (state) => this.addAt(state, this.getBraces(state)), '!': (state) => this.addBang(state, this.getBraces(state)), '*': (state) => this.repeat(state), From ad4abddf9a0ac811a71f0bb161f90a45d67b8a0a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 19 Jun 2026 07:33:38 -0400 Subject: [PATCH 4/4] Improve removal of outer braces and add a new test --- testsuite/tests/input/tex/Base.test.ts | 4 ++++ .../input/tex/__snapshots__/Base.test.ts.snap | 18 ++++++++++++++++++ ts/input/tex/ColumnParser.ts | 16 ++++++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/testsuite/tests/input/tex/Base.test.ts b/testsuite/tests/input/tex/Base.test.ts index f31218918..f31feae31 100644 --- a/testsuite/tests/input/tex/Base.test.ts +++ b/testsuite/tests/input/tex/Base.test.ts @@ -4014,6 +4014,10 @@ describe('Complete Array', () => { expect(tex2mml('\\begin{array}{{{c}}} X \\end{array}')).toMatchSnapshot(); }); + it('column {r}c{l}', () => { + expect(tex2mml('\\begin{array}{{r}c{l}} X & Y & Z \\end{array}')).toMatchSnapshot(); + }); + it('column newline', () => { expect(tex2mml('\\begin{array}{c\nc} X & Y\\end{array}')).toMatchSnapshot(); }); diff --git a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap index 745260a02..c579c43ed 100644 --- a/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap +++ b/testsuite/tests/input/tex/__snapshots__/Base.test.ts.snap @@ -1926,6 +1926,24 @@ exports[`Complete Array column {cc} 1`] = ` " `; +exports[`Complete Array column {r}c{l} 1`] = ` +" + + + + X + + + Y + + + Z + + + +" +`; + exports[`Complete Array column b 1`] = ` " diff --git a/ts/input/tex/ColumnParser.ts b/ts/input/tex/ColumnParser.ts index cc41977e4..5452bf0c8 100644 --- a/ts/input/tex/ColumnParser.ts +++ b/ts/input/tex/ColumnParser.ts @@ -111,12 +111,6 @@ export class ColumnParser { * @param {ArrayItem} array The ArrayItem for the template */ public process(parser: TexParser, template: string, array: ArrayItem) { - // - // Remove one pair of braces, if there are any - // - if (template.charAt(0) === '{' && template.slice(-1) === '}') { - template = template.slice(1, -1); - } // // Initialize the state // @@ -136,6 +130,16 @@ export class ColumnParser { cextra: array.cextra, }; // + // Remove one pair of braces, if there are any + // + if (template.charAt(0) === '{' && template.slice(-1) === '}') { + const braced = this.getBraces(state); + if (braced.length === template.length - 2) { + state.template = braced; + } + state.i = 0; + } + // // Loop through the template to process the column specifiers // let n = 0;