UNPKG

@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
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();