Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: ensure correct handling of mtable in LaTeX output #24

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1167,4 +1167,23 @@ describe('#convert', () => {
'V_{i} \\frac{\\Delta C_{A , i}^{t}}{\\Delta t} = \\sum_{j = k}^{N} G_{i , j}^{D} \\left(C_{A , j} - C_{A , i}\\right)',
);
});
describe('A mtable convertion example', () => {
it('Mtable with many attributes', () => {
const mathml =
'<math xmlns="http://www.w3.org/1998/Math/MathML"><mtable columnwidth="3em 0.05em 3em 3em 3em 3em" columnspacing="0.0em" rowspacing="0.05ex" rowlines="solid" columnlines="solid" frame="solid" framespacing="0.0em 0ex"><mtr><mtd><mi>x</mi></mtd><mtd><mspace width="0.0em" height="2.5ex" depth="1.0ex"/></mtd><mtd><mo>&#x2212;</mo><mn>1</mn></mtd><mtd><mn>0</mn></mtd><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd></mtr><mtr><mtd><mi>g</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></mtd><mtd><mspace width="0.0em" height="2.5ex" depth="1.0ex"/></mtd><mtd><mn>6</mn></mtd><mtd><mn>4</mn></mtd><mtd><mn>2</mn></mtd><mtd><mo>&#x2212;</mo><mn>1</mn></mtd></mtr><mtr><mtd><msup><mi>g</mi><mo></mo></msup><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></mtd><mtd><mspace width="0.0em" height="2.5ex" depth="1.0ex"/></mtd><mtd><mo>&#x2212;</mo><mn>1</mn></mtd><mtd><mo>&#x2212;</mo><mn>7</mn></mtd><mtd><mo>&#x2212;</mo><mn>2</mn></mtd><mtd><mo>&#x2212;</mo><mn>3</mn></mtd></mtr></mtable></math>';
const result = MathMLToLaTeX.convert(mathml);
//console.log('result', result)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead commented code

expect(result).toMatch(
'\\begin{array}{|c|c|c|c|c|c|}\\hline\\hspace{1.269cm}{x }&\\hspace{0.021150000000000002cm}{ }&\\hspace{1.269cm}{ - 1 }&\\hspace{1.269cm}{ 0 }&\\hspace{1.269cm}{ 1 }&\\hspace{1.269cm}{ 2 } \\rule{0pt}{0.5pt}\\\\ \\hline \\\\\\hspace{1.269cm}{ g \\left(\\right. x \\left.\\right) }&\\hspace{0.021150000000000002cm}{ }&\\hspace{1.269cm}{ 6 }&\\hspace{1.269cm}{ 4 }&\\hspace{1.269cm}{ 2 }&\\hspace{1.269cm}{ - 1 } \\rule{0pt}{0.5pt}\\\\ \\hline \\\\\\hspace{1.269cm}{ g^{} \\left(\\right. x \\left.\\right) }&\\hspace{0.021150000000000002cm}{ }&\\hspace{1.269cm}{ - 1 }&\\hspace{1.269cm}{ - 7 }&\\hspace{1.269cm}{ - 2 }&\\hspace{1.269cm}{ - 3} \\rule{0pt}{0.5pt}\\\\ \\hline\\end{array}',
);
});
});
it('add begin array at the front', () => {
const mathml = mathmlStrings.mtable;
const result = MathMLToLaTeX.convert(mathml);
console.log(result);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot console.log

expect(result).toMatch(
'\\left(\\right. \\begin{array}{ccc}{1 }&{ 2 }&{ 3 } \\\\{ 4 }&{ 5 }&{ 6 } \\\\{ 7 }&{ 8 }&{ 9}\\end{array} \\left.\\right)',
);
});
});
45 changes: 45 additions & 0 deletions __tests__/mocks/mathmlStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,51 @@ export const mfrac = `
</math>
</root>
`;
export const mtable = `
<root>
<math>
<mrow>
<mo>(</mo>
<mtable>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>2</mn>
</mtd>
<mtd>
<mn>3</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>4</mn>
</mtd>
<mtd>
<mn>5</mn>
</mtd>
<mtd>
<mn>6</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>7</mn>
</mtd>
<mtd>
<mn>8</mn>
</mtd>
<mtd>
<mn>9</mn>
</mtd>
</mtr>
</mtable>
<mo>)</mo>
</mrow>
</math>
</root>
`;

export const mfracWithMrow = `
<root>
Expand Down
171 changes: 159 additions & 12 deletions src/data/usecases/mathml-to-latex-convertion/converters/mtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ export class MTable implements ToLaTeXConverter {

constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
this._addFlagRecursiveIfName(this._mathmlElement.children, 'mtable', 'innerTable');
}
// new method to turn string to numbers
extractAndConvertNumbers(dimensionstring: string | undefined) {
if (!dimensionstring) {
//console.warn('dimensionString is undefined or null, default to 0.0');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead console.log

dimensionstring = '0.0, 0.0, 0.0, 0.0';
}
const numberPattern = /-?\d+(\.\d+)?([eE][+-]?\d+)?/g;
const matches = dimensionstring.match(numberPattern);
return matches ? matches.map((num) => parseFloat(num)) : [];
}

convert(): string {
Expand All @@ -16,21 +25,159 @@ export class MTable implements ToLaTeXConverter {
.map((converter) => converter.convert())
.join(' \\\\\n');

return this._hasFlag('innerTable') ? this._wrap(tableContent) : tableContent;
const Maxcol: number = this._mathmlElement.children
.map((e: MathMLElement): number => e.children.length)
.reduce((a: number, b: number) => (a >= b ? a : b));
return this._wrapNestedTableContent(tableContent, Maxcol);
}

private _wrap(latex: string): string {
return `\\begin{matrix}${latex}\\end{matrix}`;
}
private _wrapNestedTableContent(latex: string, Maxcol: number): string {
//Calculate how many ccc do we need
const col = Array.from({ length: Maxcol })
.map((e): string => 'c')
.join('');

//columnwidth
const columnwidth = this._mathmlElement.attributes['columnwidth'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary comment and case: columnwidth -> columnWidth

const widthValues = this.extractAndConvertNumbers(columnwidth);
const columnwidthCm = widthValues.map((value) => value * 0.423);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TableRows -> tableRows

let TableRows = latex.split('\\\\');
TableRows = TableRows.map((row) => {
if (row.trim() !== '') {
let columns = row.split('&');
columns = columns.map((column, index) => {
if (index < columnwidthCm.length) {
return `\\hspace{${columnwidthCm[index]}cm}{${column}}`;
}

private _addFlagRecursiveIfName(mathmlElements: MathMLElement[], name: string, flag: string): void {
mathmlElements.forEach((mathmlElement) => {
if (mathmlElement.name === name) mathmlElement.attributes[flag] = flag;
this._addFlagRecursiveIfName(mathmlElement.children, name, flag);
return column;
});
return columns.join('&');
}
return row;
});
}
latex = TableRows.join(' \\\\');

//columnspacing
const columnspacing = this._mathmlElement.attributes['columnspacing'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case

// 1 em = 0.423 cm
const columnspacingValue = parseFloat(columnspacing);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case

const columnspacingCm = isNaN(columnspacingValue) ? 0 : columnspacingValue * 0.423;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case

let tableRows = latex.split('\\\\');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the semantic diff between tableRows and TableRows?


// Process each row to add the horizontal space using \hspace
tableRows = tableRows.map((row) => {
if (row.trim() !== '') {
let columns = row.split('&');

// Add the \hspace between each column
columns = columns.map((column, index) => {
if (index < columns.length - 1)
if (!/\\hspace{.*?}/.test(column)) {
return `${column} \\hspace{${columnspacingCm}cm}`;
} else {
return column;
}
return column;
});

return columns.join('&');
}
return row;
});

latex = tableRows.join(' \\\\');

// rowspacing
const rowspacing = this._mathmlElement.attributes['rowspacing'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncessary comment

let rows = latex.split('\\\\');
rows = rows.map((row) => {
if (row.trim() !== '') {
// Convert rowspacing from em to pt assuming 1em = 10pt (need furthur adjustment based on actual font size)
const rowspacingPt: number = parseFloat(rowspacing || '0.0') * 10;
// Add the \rule to the end of the row to create vertical space
if (rowspacingPt === 0.0) {
return `${row}`;
} else {
return `${row} \\rule{0pt}{${rowspacingPt}pt}`;
}
}
return row;
});
latex = rows.join(' \\\\');
//

//rowlines
const rowlines = this._mathmlElement.attributes['rowlines'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncessary comment

if (rowlines === 'solid') {
let rows = latex.split('\\\\');

// Add \hline to the end of each row and join them back together
rows = rows.map((row) => row.trim() + '\\\\ \\hline');
latex = rows.join(' \\\\');
}

//
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncessary comment


//columnlines
const columnlines = this._mathmlElement.attributes['columnlines'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncessary comment

let columnSpec = col;
if (columnlines === 'solid') {
columnSpec = columnSpec.split('').join('|');
}
//

// frame
let hLine = '';
const frame = this._mathmlElement.attributes['frame'];
if (frame === 'solid') {
columnSpec = '|' + columnSpec + '|';
hLine = '\\hline';
}
//

//framespacing
const framespacing = this._mathmlElement.attributes['framespacing'];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncessary comment
case

const spacingValues = this.extractAndConvertNumbers(framespacing);
const horizontalSpacingEm = spacingValues[0] || 0;
const verticalSpacingEx = spacingValues[1] || 0;
let Tablerows = latex.split('\\\\');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case

// // Add vertical spacing to the first row
if (Tablerows.length > 0) {
Tablerows[0] = `\\rule{0pt}{${verticalSpacingEx}ex} ` + Tablerows[0];
}
// // Add vertical spacing to the last row
if (Tablerows.length > 1) {
Tablerows[Tablerows.length - 1] += ` \\rule{0pt}{${verticalSpacingEx}ex}`;
}
Tablerows = Tablerows.map((row, rowIndex) => {
if (row.trim() !== '') {
let columns = row.split('&');

// Add horizontal spacing to the first column if \hspace does not already exist
if (!/\\hspace{.*?}/.test(columns[0])) {
if (horizontalSpacingEm === 0) {
return columns[0];
}
columns[0] = `\\hspace{${horizontalSpacingEm}em} ${columns[0]}`;
}

// Add horizontal spacing to the last column if \hspace does not already exist
if (!/\\hspace{.*?}/.test(columns[columns.length - 1])) {
if (horizontalSpacingEm === 0) {
return columns[columns.length - 1];
}
columns[columns.length - 1] = `${columns[columns.length - 1]} \\hspace{${horizontalSpacingEm}em}`;
}

return columns.join('&');
}
return row;
});

latex = latex.replace(/\\hspace\{0cm}/g, '');

private _hasFlag(flag: string): boolean {
return !!this._mathmlElement.attributes[flag];
const screen_output = `\\begin{array}{${columnSpec}}${hLine}${latex}\\end{array}`;
return screen_output;
}
}
Loading