@wiris/mathtype-html-integration-devkit
Version:
Allows to integrate MathType Web into any JavaScript HTML WYSIWYG rich text editor.
307 lines (266 loc) • 9.65 kB
JavaScript
import TextCache from "./textcache";
import MathML from "./mathml";
import ServiceProvider from "./serviceprovider";
import Constants from "./constants";
import Util from "./util";
/**
* @classdesc
* This class represents a LaTeX parser. Manages the services which allows to convert
* LaTeX into MathML and MathML into LaTeX.
*/
export default class Latex {
/**
* Static property.
* Return latex cache.
* @private
* @type {Cache}
*/
static get cache() {
return Latex._cache;
}
/**
* Static property setter.
* Set latex cache.
* @param {Cache} value - The property value.
* @ignore
*/
static set cache(value) {
Latex._cache = value;
}
/**
* Converts MathML to LaTeX by calling mathml2latex service. For text services
* we call a text service with the param mathml2latex.
* @param {String} mathml - MathML String.
* @return {String} LaTeX string generated by the MathML argument.
*/
static getLatexFromMathML(mathml) {
const mathmlWithoutSemantics = MathML.removeSemantics(mathml);
/**
* @type {TextCache}
*/
const { cache } = Latex;
const data = {
service: "mathml2latex",
mml: mathmlWithoutSemantics,
};
const jsonResponse = JSON.parse(ServiceProvider.getService("service", data));
// TODO: Error handling.
let latex = "";
if (jsonResponse.status === "ok") {
latex = jsonResponse.result.text;
const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);
// Inserting LaTeX semantics.
const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, "LaTeX");
cache.populate(latex, mathmlWithSemantics);
}
return latex;
}
/**
* Converts LaTeX to MathML by calling latex2mathml service. For text services
* we call a text service with the param latex2mathml.
* @param {String} latex - String containing a LaTeX formula.
* @param {Boolean} includeLatexOnSemantics
* - If true LaTeX would me included into MathML semantics.
* @return {String} MathML string generated by the LaTeX argument.
*/
static getMathMLFromLatex(latex, includeLatexOnSemantics) {
/**
* @type {TextCache}
*/
const latexCache = Latex.cache;
if (Latex.cache.get(latex)) {
return Latex.cache.get(latex);
}
const data = {
service: "latex2mathml",
latex,
};
if (includeLatexOnSemantics) {
data.saveLatex = "";
}
const jsonResponse = JSON.parse(ServiceProvider.getService("service", data));
let output;
if (jsonResponse.status === "ok") {
let mathml = jsonResponse.result.text;
mathml = mathml.split("\r").join("").split("\n").join(" ");
// Populate LatexCache.
if (mathml.indexOf("semantics") === -1 && mathml.indexOf("annotation") === -1) {
const content = Util.htmlEntities(latex);
mathml = MathML.addAnnotation(mathml, content, "LaTeX");
output = mathml;
} else {
output = mathml;
}
if (!latexCache.get(latex)) {
latexCache.populate(latex, mathml);
}
} else {
output = `$$${latex}$$`;
}
return output;
}
/**
* Converts all occurrences of MathML code to LaTeX.
* The MathML code should containing <annotation encoding="LaTeX"/> to be converted.
* @param {String} content - A string containing MathML valid code.
* @param {Object} characters - An object containing special characters.
* @return {String} A string containing all MathML annotated occurrences
* replaced by the corresponding LaTeX code.
*/
static parseMathmlToLatex(content, characters) {
let output = "";
const mathTagBegin = `${characters.tagOpener}math`;
const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;
const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;
const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;
let start = content.indexOf(mathTagBegin);
let end = 0;
let mathml;
let startAnnotation;
let closeAnnotation;
while (start !== -1) {
output += content.substring(end, start);
end = content.indexOf(mathTagEnd, start);
if (end === -1) {
end = content.length - 1;
} else {
end += mathTagEnd.length;
}
mathml = content.substring(start, end);
startAnnotation = mathml.indexOf(openTarget);
if (startAnnotation !== -1) {
startAnnotation += openTarget.length;
closeAnnotation = mathml.indexOf(closeTarget);
let latex = mathml.substring(startAnnotation, closeAnnotation);
if (characters === Constants.safeXmlCharacters) {
latex = MathML.safeXmlDecode(latex);
}
output += `$$${latex}$$`;
// Populate latex into cache.
Latex.cache.populate(latex, mathml);
} else {
output += mathml;
}
start = content.indexOf(mathTagBegin, end);
}
output += content.substring(end, content.length);
return output;
}
/**
* Extracts the latex of a determined position in a text.
* @param {Node} textNode - textNode to extract the LaTeX
* @param {Number} caretPosition - Starting position to find LaTeX.
* @param {Object} latexTags - Optional parameter representing tags between latex is inserted.
* It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.
* "$$" by default.
* @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.
* @static
*/
static getLatexFromTextNode(textNode, caretPosition, latexTags) {
// TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).
// Tags used for LaTeX formulas.
const defaultLatexTags = {
open: "$$",
close: "$$",
};
// latexTags is an optional parameter. If is not set, use default latexTags.
if (typeof latexTags === "undefined" || latexTags == null) {
latexTags = defaultLatexTags;
}
// Looking for the first textNode.
let startNode = textNode;
while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {
// TEXT_NODE.
startNode = startNode.previousSibling;
}
/**
* Returns the next latex position and node from a specific node and position.
* @param {Node} currentNode - Node where searching latex.
* @param {Number} currentPosition - Current position inside the currentNode.
* @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.
* "$$" by default.
* @param {Boolean} tag - Tag containing the current search.
* @returns {Object} Object containing the current node and the position.
*/
function getNextLatexPosition(currentNode, currentPosition, tag) {
let position = currentNode.nodeValue.indexOf(tag, currentPosition);
while (position === -1) {
currentNode = currentNode.nextSibling;
if (!currentNode) {
// TEXT_NODE.
return null; // Not found.
}
position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;
}
return {
node: currentNode,
position,
};
}
/**
* Determines if a node is previous, or not, to a second one.
* @param {Node} node - Start node.
* @param {Number} position - Start node position.
* @param {Node} endNode - End node.
* @param {Number} endPosition - End node position.
* @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.
*/
function isPrevious(node, position, endNode, endPosition) {
if (node === endNode) {
return position <= endPosition;
}
while (node && node !== endNode) {
node = node.nextSibling;
}
return node === endNode;
}
let start;
let end = {
node: startNode,
position: 0,
};
// Is supposed that open and close tags has the same length.
const tagLength = latexTags.open.length;
do {
start = getNextLatexPosition(end.node, end.position, latexTags.open);
if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {
return null;
}
end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);
if (end == null) {
return null;
}
end.position += tagLength;
} while (isPrevious(end.node, end.position, textNode, caretPosition));
// Isolating latex.
let latex;
if (start.node === end.node) {
latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);
} else {
const index = start.position + tagLength;
latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);
let currentNode = start.node;
do {
currentNode = currentNode.nextSibling;
if (currentNode === end.node) {
latex += end.node.nodeValue.substring(0, end.position - tagLength);
} else {
latex += currentNode.nodeValue ? currentNode.nodeValue : "";
}
} while (currentNode !== end.node);
}
return {
latex,
startNode: start.node,
startPosition: start.position,
endNode: end.node,
endPosition: end.position,
};
}
}
/**
* Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.
* @type {Cache}
* @static
*/
Latex._cache = new TextCache();