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: use first content element if no apply element is present #25

Merged
merged 1 commit into from
Dec 20, 2017
Merged
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
10 changes: 5 additions & 5 deletions api/v1/controller/AbstractSyntaxTreeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
.then((ast) => {
res.json(ast);
})
.catch((err) => { next(Boom.badData(err.message, JSON.stringify(err))); });
.catch((err) => { next(Boom.badData(err.message, res.locals.mathml.toLocaleString())); });
},

parseCytoscapedAST: (req, res, next) => {
Expand Down Expand Up @@ -71,7 +71,7 @@ module.exports = {
});
})
.catch((err) => {
next(Boom.badData(err.message));
next(Boom.badData(err.message, res.locals.mathml));
});
},

Expand All @@ -85,7 +85,7 @@ module.exports = {
}).then((cytoscapedMergedAST) => {
res.json({ cytoscapedMergedAST });
})
.catch((err) => { next(Boom.badData(err.message, JSON.stringify(err))); });
.catch((err) => { next(Boom.badData(err.message)); });
},

renderMergedAst:
Expand All @@ -102,14 +102,14 @@ module.exports = {
res.sendFile(tmpFilename);
});
})
.catch((err) => { next(Boom.badData(err.message, JSON.stringify(err))); });
.catch((err) => { next(Boom.badData(err.message)); });
},

renderMML:
(req, res, next) => {
MathJaxRenderer.renderMML(req.body.mathml).then((svg) => {
res.send(svg);
})
.catch((err) => { next(Boom.badData(err.message, JSON.stringify(err))); });
.catch((err) => { next(Boom.badData(err.message, req.body.mathml.toLocaleString())); });
}
};
9 changes: 9 additions & 0 deletions data/6-single-element.mml.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<math xmlns="http://www.w3.org/1998/Math/MathML" id="p1.1.m1.1" class="ltx_Math" alttext="m" display="inline">
<semantics id="p1.1.m1.1a">
<mi id="p1.1.m1.1.1" xref="p1.1.m1.1.1.cmml">m</mi>
<annotation-xml encoding="MathML-Content" id="p1.1.m1.1b">
<ci id="p1.1.m1.1.1.cmml" xref="p1.1.m1.1.1">𝑚</ci>
</annotation-xml>
<annotation encoding="application/x-tex" id="p1.1.m1.1c">m</annotation>
</semantics>
</math>
3 changes: 2 additions & 1 deletion errorHandler/ErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const log = require('lib/logger');
const Boom = require('boom');
const CircularJSON = require('circular-json');

module.exports = (app) => {
// catch 404s
Expand All @@ -10,7 +11,7 @@ module.exports = (app) => {
});

app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
log.error(err);
log.error(CircularJSON.stringify(err));
const BoomError = !err.isBoom ? Boom.wrap(err) : err;
if (res.headersSent) {
app.log('Skip error handler.');
Expand Down
73 changes: 49 additions & 24 deletions lib/ASTParser/ASTParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,83 @@ const xmldom = require('xmldom');
const xpath = require('xpath');
const opMap = require('./operationMap');
const Serializer = xmldom.XMLSerializer;
const log = require('lib/logger');

module.exports = class ASTParser {
constructor(xml, options = {}) {
this.doc = xml;
this.doc = this.clean(xml);
this.select = xpath.useNamespaces({ m: 'http://www.w3.org/1998/Math/MathML' });
this.options = Object.assign({
collapseSingleOperandNodes: true,
nodesToBeCollapsed: []
}, options);
}
// from https://www.sitepoint.com/removing-useless-nodes-from-the-dom/
clean(node) {
for (let n = 0; n < node.childNodes.length; n++) {
const child = node.childNodes[n];
if (child.nodeType === 8 || (child.nodeType === 3 && !/\S/.test(child.nodeValue))) {
node.removeChild(child);
n--;
} else if (child.nodeType === 1) {
this.clean(child);
}
}
return node;
}

parse() {
return new Promise((resolve, reject) => {
try {
const content = this.select('//m:annotation-xml[@encoding="MathML-Content"]', this.doc)[0];
const result = this.parseApply(this.select('m:apply', content)[0]);
resolve(result);
const mainElement = this.getMainElement();
resolve(mainElement);
} catch (e) {
log.error(e);
reject(e);
}
});
}

getMainElement() {
const content = this.select('//m:annotation-xml[@encoding="MathML-Content"]', this.doc, true);
if (!content) {
throw new Error("No content MathML present");
}
return this.parseApply(content.firstChild);
}

parseApply(applyElement) {
const childElements = this.select('*', applyElement);
const operationElement = childElements.shift();
const operationElement = childElements.shift() || applyElement;

// Lookup operation name from Presentation MathML,
// fallback on tagName if no xref attribute present
const opXrefAttr = this.select('@xref', operationElement)[0];
const opXref = opXrefAttr ? opXrefAttr.value : null;
const opXrefAttr = operationElement.getAttribute('xref');
const opXref = opXrefAttr || null;
const opName = operationElement.tagName;

// presentation MathML for the operation Element
const opPresentationElement = opMap.hasOwnProperty(opName) ? opMap[opName] : this.getElementById(opXref);

// presentation mathml for full subtree
let presentationElement = null;
const applyXref = this.select('@xref', applyElement)[0];
if (applyXref) { presentationElement = this.getElementById(applyXref.value); }
const applyXref = applyElement.getAttribute('xref');
if (applyXref) { presentationElement = this.getElementById(applyXref); }
if (!applyElement.hasAttribute('id')) {
throw new Error('MathML apply elements don\'t have ids.');
}
const applyId = this.select('@id', applyElement)[0].value;
const applyId = applyElement.getAttribute('id');

const operationCd = this.select('@cd', operationElement);
const operationCd = operationElement.getAttribute('cd');

// return whole subtree mathml if element is marked ambiguous
if (operationCd.length > 0 && operationCd[0].value === 'ambiguous') {
if (operationCd === 'ambiguous') {
return {
name: 'ambiguous',
presentation: this.constructor.serialize(presentationElement),
nodePresentation: this.constructor.serialize(presentationElement),
id: applyId,
presentation_id: applyXref.value
presentation_id: applyXref
};
}

Expand All @@ -69,16 +91,16 @@ module.exports = class ASTParser {
!this.isNodeToBeCollapsed(element)) {
return this.parseApply(element);
}
const xref = this.select('@xref', element);
const elementId = this.select('@id', element)[0].value;
const xref = element.getAttribute('xref');
const elementId = element.getAttribute('id');

let name = null;
let leafPresentation = null;
if (xref.length > 0) {
leafPresentation = this.getElementById(xref[0].value);
name = this.getXrefTextContent(xref[0].value);
leafPresentation = this.getElementById(xref);
name = this.getXrefTextContent(xref);
} else {
name = this.select('text()', element);
name = element.textContent;// this.select('text()', element);
}
const cd = element.getAttribute('cd');
let qId = 0;
Expand All @@ -92,7 +114,7 @@ module.exports = class ASTParser {
name,
id: elementId,
qId,
presentation_id: xref[0].value
presentation_id: xref
};
});

Expand All @@ -102,7 +124,7 @@ module.exports = class ASTParser {
nodePresentation: this.constructor.serialize(opPresentationElement),
children,
id: applyId,
presentation_id: applyXref.value
presentation_id: applyXref
};
}

Expand All @@ -125,15 +147,18 @@ module.exports = class ASTParser {
}

getXrefTextContent(xref) {
const textContent = this.select(`//*[@id="${xref}"]/text()`, this.doc);
return textContent.length > 0 ? textContent[0].nodeValue : 'collapsed content';
const textElement = this.select(`//*[@id="${xref}"]/text()`, this.doc, true);
if (textElement) {
return textElement.textContent;
}
return 'collapsed content';
}

getElementById(xref) {
return this.select(`//*[@id="${xref}"]`, this.doc)[0];
return this.select(`//*[@id="${xref}"]`, this.doc, true);
}

isNodeToBeCollapsed(element) {
return this.options.nodesToBeCollapsed.includes(this.select('@id', element)[0].value);
return this.options.nodesToBeCollapsed.includes(element.getAttribute('id'));
}
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
"base64-stream": "^0.1.3",
"boom": "^4.1.0",
"cheerio": "^0.22.0",
"circular-json": "^0.5.1",
"compression": "^1.6.2",
"cytoscape": "^2.7.13",
"cytoscape-qtip": "^2.7.1",
"cytosnap": "^1.1.2",
"d3": "^4.3.0",
"express": "^4.16.2",
Expand Down
8 changes: 8 additions & 0 deletions test/api/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ describe('api test', () => {
.expect(200, done);
});
});
singleEndpoints.forEach((t) => {
it('handle single mathml requests ' + t, function testSlash (done) {
request(server)
.post(`/api/v1/math/${t}`)
.field('mathml', app.locals.mml[6])
.expect(200, done);
});
});
mergedEndpoints.forEach((t) => {
it('handle mathml requests ' + t, function testSlash (done) {
request(server)
Expand Down
2 changes: 2 additions & 0 deletions vmext.iml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
<content url="file:///media/physikerwelt/Windows/git/ag-gipp/vmext" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery" level="application" />
<orderEntry type="library" name="jquery.qtip" level="application" />
</component>
</module>