@wiris/mathtype-viewer
Version:
The MathType viewer renders all MathML and LaTeX-like formulas in a webpage using the MathType rendering engine.
262 lines (227 loc) • 10.3 kB
text/typescript
import { renderLatex } from "./latex";
import { renderMathML } from "./mathml";
import { Properties } from "./properties";
/**
* Exposes the {@link JsPluginViewer} singleton instance as window.com.wiris.js.JsPluginViewer.
* @param {Properties} properties - Properties of the viewer.
* @param {Window} w - Instance of the global window.
* @deprecated Consider using {@link renderLatex} or {@link renderMathML}.
*/
export function bypassEncapsulation(properties: Properties, w: Window) {
const wany = w as any;
if (typeof wany.com === "undefined") {
wany.com = {};
}
if (typeof wany.com.wiris === "undefined") {
wany.com.wiris = {};
}
if (typeof wany.com.wiris.js === "undefined") {
wany.com.wiris.js = {};
}
if (typeof wany.com.wiris.js.JsPluginViewer === "undefined") {
wany.com.wiris.js.JsPluginViewer = JsPluginViewer.getInstance();
JsPluginViewer.properties = properties;
}
}
interface MathMLPosition {
nextElement: Node;
safeMML: string;
}
/**
* This class is a compatibility layer with the old Viewer.
* See haxe/src-haxe/com/wiris/js/JsPluginViewer.hx in in the repo wiris/plugins previous to commit df1248a.
*
* WARNING: the methods in this class may contain direct references to globals such as window and document.
*
* @deprecated Consider using {@link renderLatex} or {@link renderMathML}.
*/
class JsPluginViewer {
static instance: JsPluginViewer;
static properties: Properties;
static getInstance(): JsPluginViewer {
if (JsPluginViewer.instance == null) {
JsPluginViewer.instance = new JsPluginViewer();
}
return JsPluginViewer.instance;
}
/**
* Render all the formulas written in SafeMathML inside the given element.
* @param {HTMLElement} element - Element wherein to render SafeMathML formulas.
* @param asynchronously - Currently ignored, only included for retrocompatibility purposes.
* @param callbackFunc - Currently ignored, only included for retrocompatibility purposes.
* @deprecated There is currently no replacement for rendering SafeMathML formulas.
* Please consider using {@link renderLatex} or {@link renderMathML}.
*/
parseSafeMathMLElement(element: HTMLElement, asynchronously?: boolean, callbackFunc?: () => void): void {
var mathmlPositions: MathMLPosition[] = [];
JsPluginViewer.getMathMLPositionsAtElementAndChildren(element, mathmlPositions);
for (let i = 0; i < mathmlPositions.length; i++) {
var mathmlPosition = mathmlPositions[i];
var newNode = document.createElement("math");
mathmlPosition.nextElement.parentNode?.insertBefore(newNode, mathmlPosition.nextElement);
newNode.outerHTML = JsPluginViewer.decodeSafeMathML(mathmlPosition.safeMML);
}
}
/**
* Render all the formulas in the document.
* @param asynchronously - Currently ignored, only included for retrocompatibility purposes.
* @param callbackFunc - Currently ignored, only included for retrocompatibility purposes.
* @param safeXml - Currently ignored, only included for retrocompatibility purposes.
* @deprecated Please consider using {@link renderMathML}.
*/
async parseDocument(asynchronously?: boolean, callbackFunc?: () => void, safeXml?: boolean): Promise<void> {
return renderMathML(JsPluginViewer.properties, document.documentElement);
}
/**
* Render all the formulas inside the given element.
* @param {HTMLElement} element - Element wherein to render formulas.
* @param asynchronously - Currently ignored, only included for retrocompatibility purposes.
* @param callbackFunc - Currently ignored, only included for retrocompatibility purposes.
* @deprecated Please consider using {@link renderMathML}.
*/
async parseElement(element: HTMLElement, asynchronously?: boolean, callbackFunc?: () => void): Promise<void> {
await renderLatex(JsPluginViewer.properties, element);
return await renderMathML(JsPluginViewer.properties, element);
}
/**
* Convert all the LaTeX formulas in the document to MathML.
* @param asynchronously - Currently ignored, only included for retrocompatibility purposes.
* @param callbackFunc - Currently ignored, only included for retrocompatibility purposes.
* @deprecated Please consider using {@link renderLatex}.
*/
async parseLatexDocument(asynchronously?: boolean, callbackFunc?: () => void): Promise<void> {
return renderLatex(JsPluginViewer.properties, document.documentElement);
}
/**
* Convert all the LaTeX formulas inside the given element to MathML.
* @param {HTMLElement} element - Element wherein to convert formulas.
* @param asynchronously - Currently ignored, only included for retrocompatibility purposes.
* @param callbackFunc - Currently ignored, only included for retrocompatibility purposes.
* @deprecated Please consider using {@link renderLatex}.
*/
async parseLatexElement(element: HTMLElement, asynchronously?: boolean, callbackFunc?: () => void): Promise<void> {
return renderLatex(JsPluginViewer.properties, element);
}
private static decodeSafeMathML(input: string): string {
var safeXMLCharactersEntities = JsCharacters.getSafeXMLCharactersEntities();
var xmlCharacters = JsCharacters.getXMLCharacters();
var safeXMLCharacters = JsCharacters.getSafeXMLCharacters();
var tagOpenerEntity = safeXMLCharactersEntities.tagOpener;
var tagCloserEntity = safeXMLCharactersEntities.tagCloser;
var doubleQuoteEntity = safeXMLCharactersEntities.doubleQuote;
var realDoubleQuoteEntity = safeXMLCharactersEntities.realDoubleQuote;
// Important to not change function parameter.
var inputCopy = input.slice();
// Decoding entities.
inputCopy = inputCopy.split(tagOpenerEntity).join(safeXMLCharacters.tagOpener);
inputCopy = inputCopy.split(tagCloserEntity).join(safeXMLCharacters.tagCloser);
inputCopy = inputCopy.split(doubleQuoteEntity).join(safeXMLCharacters.doubleQuote);
inputCopy = inputCopy.split(realDoubleQuoteEntity).join(safeXMLCharacters.realDoubleQuote);
var tagOpener = safeXMLCharacters.tagOpener;
var tagCloser = safeXMLCharacters.tagCloser;
var doubleQuote = safeXMLCharacters.doubleQuote;
var realDoubleQuote = safeXMLCharacters.realDoubleQuote;
var ampersand = safeXMLCharacters.ampersand;
var quote = safeXMLCharacters.quote;
// Decoding characters.
inputCopy = inputCopy.split(tagOpener).join(xmlCharacters.tagOpener);
inputCopy = inputCopy.split(tagCloser).join(xmlCharacters.tagCloser);
inputCopy = inputCopy.split(doubleQuote).join(xmlCharacters.doubleQuote);
inputCopy = inputCopy.split(ampersand).join(xmlCharacters.ampersand);
inputCopy = inputCopy.split(quote).join(xmlCharacters.quote);
// We are replacing $ by & when its part of an entity for retrocompatibility.
// Now, the standard is replace § by &.
var returnValue = "";
var currentEntity: string | null = null;
var i = 0;
while (i < inputCopy.length) {
var character = inputCopy.charAt(i);
if (currentEntity == null) {
if (character == "$") {
currentEntity = "";
} else {
returnValue += character;
}
} else if (character == ";") {
returnValue += "&" + currentEntity;
currentEntity = null;
} else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {
// Character is part of an entity.
currentEntity += character;
} else {
returnValue += "$" + "currentEntity"; // Is not an entity.
currentEntity = null;
i -= 1; // Parse again the current character.
}
i++;
}
return returnValue;
}
private static getMathMLPositionsAtElementAndChildren(element: Node, mathmlPositions: MathMLPosition[]) {
JsPluginViewer.getMathMLPositionsAtNode(element, mathmlPositions);
// Copy current children because DOM will be changed and element.childNodes won't be
// consistent on call getMathMLPositionsAtElementAndChildren().
var childNodes = Array.from(element.childNodes);
if (childNodes.length > 0) {
for (let i = 0; i < childNodes.length; i++) {
var child = childNodes[i];
JsPluginViewer.getMathMLPositionsAtElementAndChildren(child, mathmlPositions);
}
}
}
private static getMathMLPositionsAtNode(node: Node, mathmlPositions) {
var safeXMLCharacters = JsCharacters.getSafeXMLCharacters();
if (node.nodeType == 3) {
var startMathmlTag = safeXMLCharacters.tagOpener + "math";
var endMathmlTag = safeXMLCharacters.tagOpener + "/math" + safeXMLCharacters.tagCloser;
var start = node.textContent?.indexOf(startMathmlTag) ?? -1;
var end = 0;
while (start != -1) {
end = node.textContent?.indexOf(endMathmlTag, start + startMathmlTag.length) ?? -1;
if (end == -1) break;
var nextMathML = node.textContent?.indexOf(startMathmlTag, end + endMathmlTag.length) ?? -1;
if (nextMathML >= 0 && end > nextMathML) break;
var safeMathml = node.textContent?.substring(start, end + endMathmlTag.length);
node.textContent =
(node.textContent?.substring(0, start) ?? "") + node.textContent?.substring(end + endMathmlTag.length);
node = (node as Text).splitText(start);
start = node.textContent?.indexOf(startMathmlTag) ?? -1;
mathmlPositions.push({
safeMML: safeMathml,
nextElement: node,
});
}
}
}
}
class JsCharacters {
static getSafeXMLCharactersEntities(): any {
return {
tagOpener: "«",
tagCloser: "»",
doubleQuote: "¨",
realDoubleQuote: """,
};
}
static getXMLCharacters(): any {
return {
id: "xmlCharacters",
tagOpener: "<", // Hex: \x3C.
tagCloser: ">", // Hex: \x3E.
doubleQuote: '"', // Hex: \x22.
ampersand: "&", // Hex: \x26.
quote: "'", // Hex: \x27.
};
}
static getSafeXMLCharacters(): any {
return {
id: "safeXmlCharacters",
tagOpener: "«", // Hex: \xAB.
tagCloser: "»", // Hex: \xBB.
doubleQuote: "¨", // Hex: \xA8.
ampersand: "§", // Hex: \xA7.
quote: "`", // Hex: \x60.
realDoubleQuote: "¨",
};
}
}