Skip to content
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
28 changes: 22 additions & 6 deletions src/render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ const getFormat = (input) => {
}
};

/**
* Validate MathML string.
*
* @param {string} math - The input MathML string to validate.
* @param {Promise} conversionPromise - The MathJax conversion function to use for validation.
* @returns {Promise<node: string}>} The MathJax conversion promise
* @throws {Error} Throws an error if input string is not valid MathML.
*/
const validateMathML = async (math, conversionPromise) => {
const p = conversionPromise(math);
try {
await p;
} catch(err) {
throw new Error(`Invalid MathML: ${err.message}`);
}
return p;
}

/**
* Typeset math.
*
Expand All @@ -85,7 +103,10 @@ const typeset = async (data) => {
throw new Error(`Supported output formats for ${data.format} input are: MathML, SVG`);
case 'MathML':
if (data.svg) {
return MathJax.mathml2svgPromise(data.math);
return await validateMathML(data.math, MathJax.mathml2svgPromise);
}
if (data.mml) {
return await validateMathML(data.math, MathJax.mathml2mmlPromise);
}
throw new Error(`Supported output formats for ${data.format} input are: SVG`);
default:
Expand Down Expand Up @@ -116,11 +137,6 @@ const typeset = async (data) => {
* @returns {Promise<Output>}
*/
exports.render = async (event) => {
if (event.input === 'mathml' && event.output === 'mathml') {
// No-op conversion MathML-to-MathML.
return { contentType: RESPONSE_TYPES.mathml, data: event.source };
}

const format = getFormat(event);
const math = event.source;
switch (event.output) {
Expand Down
42 changes: 42 additions & 0 deletions test/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ describe('GET /render', function () {
.expect(400, { message: 'Invalid output: INVALID' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});

it('should return "400 Bad Request" with invalid MathML source (xml validation error)', function () {
const search = { input: 'mathml', output: 'mathml', source: 'x^2' };
const url = `/render?${querystring.stringify(search)}`;

return testApp
.get(url)
.expect(400, { message: 'Invalid MathML: MathML must be formed by a <math> element, not <#text>' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});

it('should return "400 Bad Request" with invalid MathML source (MathML validation error)', function () {
const search = { input: 'mathml', output: 'mathml', source: '<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">x^2</math>' };
const url = `/render?${querystring.stringify(search)}`;

return testApp
.get(url)
.expect(400, { message: 'Invalid MathML: Unexpected text node "x^2"' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});
});

describe('POST /render', function () {
Expand Down Expand Up @@ -157,4 +177,26 @@ describe('POST /render', function () {
.expect(400, { message: 'Invalid output: INVALID' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});

it('should return "400 Bad Request" with invalid MathML source (xml validation error)', function () {
const body = { input: 'mathml', output: 'mathml', source: 'x^2' };

return testApp
.post('/render')
.set('Content-Type', 'application/json')
.send(body)
.expect(400, { message: 'Invalid MathML: MathML must be formed by a <math> element, not <#text>' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});

it('should return "400 Bad Request" with invalid MathML source (MathML validation error)', function () {
const body = { input: 'mathml', output: 'mathml', source: '<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">x^2</math>' };

return testApp
.post('/render')
.set('Content-Type', 'application/json')
.send(body)
.expect(400, { message: 'Invalid MathML: Unexpected text node "x^2"' })
.expect('Content-Type', /^application\/json(?:;|$)/);
});
});
18 changes: 16 additions & 2 deletions test/render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('index#render', function () {
}
});

it('should throw with invalid source', async function () {
it('should throw with invalid xml source', async function () {
const event = { input: 'mathml', output: 'svg', source: 'x^2' };

try {
Expand All @@ -99,7 +99,21 @@ describe('index#render', function () {
} catch (err) {
expect(err).to.be.an.instanceOf(Error)
.that.has.property('message')
.that.contains('MathML must be formed by a <math> element, not <#text>')
.that.contains('Invalid MathML: MathML must be formed by a <math> element, not <#text>')
.and.that.does.not.contain('\n');
}
});

it('should throw with valid xml but invalid MathML source', async function () {
const event = { input: 'mathml', output: 'svg', source: '<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">x^2</math>' };

try {
await render(event);
expect.fail('Did not throw');
} catch (err) {
expect(err).to.be.an.instanceOf(Error)
.that.has.property('message')
.that.contains('Invalid MathML: Unexpected text node "x^2"')
.and.that.does.not.contain('\n');
}
});
Expand Down