/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (function(window) { /** * Class to work with MathML * @constructor */ function MathML() { /** * * @param {string} xml * @param {AscWord.CTextPr} [textPr=undefined] * @returns {AscWord.ParaMath} */ this.toParaMath = function(xml, textPr) { xml = xml ? xml : ""; // Replace
tags (with any attributes) with nothing, as they're not valid MathML xml = xml.replace(/]*>/gi, ''); let paraMath = new AscWord.ParaMath(); this.mathMLData = { 'mo-linebreak-todo' : [], 'mo-id' : {} }; let reader = new StaxParser(xml); if (!reader.ReadNextNode() || "math" !== reader.GetNameNoNS()) { paraMath.Root.Correct_Content(true); return paraMath; } this.handleMathContent(reader, paraMath, paraMath.Root); paraMath.Root.Correct_Content(true); paraMath.SetParagraph(null); //paraMath.applyMathMLGlobalAttributes(); if (textPr) { textPr.RFonts.SetAll("Cambria Math"); paraMath.ApplyTextPr(textPr, undefined, true); } return paraMath; }; this.addFlatToMathContent = function(mathContent, element) { // CMathContent cannot be nested inside another CMathContent if (element instanceof AscCommonWord.CMathContent) { for (let i = 0; i < element.Content.length; i++) { mathContent.addElementToContent(element.Content[i]); } } else { mathContent.addElementToContent(element); } }; this.GetAttributes = function(reader) { let attributes = reader.GetAttributes(); function valuesToLower(obj) { let out = {}; for (let k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) { let v = obj[k]; out[k] = (typeof v === 'string') ? v.toLowerCase() : v; } } return out; } return valuesToLower(attributes); }; this.proceedMathMLDefaultAttributes = function(attributes, elements, el, name) { let keysAttributes = Object.keys(attributes); for (let nAttribute = 0; nAttribute < keysAttributes.length; nAttribute++) { switch (keysAttributes[nAttribute]) { case 'mathcolor': { let rgb = AscFormat.mapPrstColor[attributes['mathcolor']]; if (rgb) { let color = new AscWord.CDocumentColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); el.Set_Color(color); } break; } case 'mathbackground': { let rgb = AscFormat.mapPrstColor[attributes['mathbackground']]; if (rgb) { let color = new AscWord.CDocumentColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); el.Set_HighLight(color); } break; } case 'mathsize': { //default size if 16px or 1em if (attributes['mathsize'] === 'normal') { el.SetFontSize(16); } else if (attributes['mathsize'] === 'small') { el.SetFontSize(12); } else if (attributes['mathsize'] === 'big') { el.SetFontSize(20); } else if (attributes['mathsize'].slice(-1) === "%" && Number(attributes['mathsize'].slice(0, -1))) { let percentage = Number(attributes['mathsize'].slice(0, -1)); let defaultvalue = 16; let value = defaultvalue * (percentage / 100); el.SetFontSize(value); } } case 'stretchy': { if (attributes['stretchy'] === 'false') { if (!el.mathml_metadata) el.mathml_metadata = {}; el.mathml_metadata.stretchy = false; } } default: break; } } elements.push(el); }; this.proceedMathMLImplicitDelimiter = function(result, start, end) { let props = new CMathDelimiterPr(); props.begChr = start ? start.charCodeAt(0) : -1; props.endChr = end ? end.charCodeAt(0) : -1; props.sepChr = ','.charCodeAt(0); props.shp = DELIMITER_SHAPE_MATCH; let mathContent = new CMathContent(); for (let i = 0; i < result.length; i++) { mathContent.addElementToContent(result[i]); } mathContent.Correct_Content(true); props.content = [mathContent]; return [this.handleDelimiter(undefined, props)]; }; this.readMathMLNode = function(reader, parentMathContent) { function decodeHexEntities(str) { var result = ''; for (var i = 0; i < str.length;) { if (str.charAt(i) === '&' && str.charAt(i + 1) === '#' && str.charAt(i + 2).toLowerCase() === 'x') { var j = i + 3; var hex = ''; while (j < str.length && str.charAt(j) !== ';') { hex += str.charAt(j); j++; } if (str.charAt(j) === ';') { var code = parseInt(hex, 16); if (!isNaN(code)) { result += String.fromCharCode(code); i = j + 1; continue; } } } result += str.charAt(i); i++; } return result; } function proceedAttributes(data) { let name = data.name; let text = data.text; let attributes = data.attributes; let oThis = data.oThis; let parentMathContent = data.parentMathContent; const GetMathFontChar = AscMath.GetMathFontChar; switch (name) { case 'mi': { let type = -1; switch (attributes['mathvariant']) { case 'bold': type = 0; break; case 'italic': type = 1; break; case 'bold-italic': type = 2; break; case 'double-struck': type = 12; break; case 'bold-fraktur': type = 10; break; case 'script': type = 7; break; case 'bold-script': type = 8; break; case 'fraktur': type = 9; break; case 'sans-serif': type = 3; break; case 'bold-sans-serif': type = 4; break; case 'sans-serif-italic': type = 5; break; case 'sans-serif-bold-italic': type = 6; break; case 'monospace': type = 11; break; default: type = -1; break; } // single character must be italic if (text.length === 1 && type === -1 && !attributes['mathvariant']) type = 1; let convertedText = ""; for (let oIter = text.getUnicodeIterator(); oIter.check(); oIter.next()) { let currentChar = String.fromCodePoint(oIter.value()); if (GetMathFontChar[currentChar] && GetMathFontChar[currentChar][type]) convertedText += GetMathFontChar[currentChar][type]; else convertedText += currentChar; } text = convertedText; break; } case 'mo': { if (attributes['id']) oThis.mathMLData['mo-id'][attributes['id']] = elements[0]; if (attributes['linebreak']) { oThis.mathMLData['mo-linebreak-todo'].push({ element: elements[0], type: attributes['linebreak'], indentalign: attributes['indentalign'] === "id", indenttarget: attributes['indenttarget'] }); } // indent if (attributes['movablelimits'] && parentMathContent) { parentMathContent.mathml_metadata['movablelimits'] = attributes['movablelimits']; } break; } case 'mspace': { text = ""; break; } case 'ms': { if (attributes['lquote']) { text = attributes['lquote'] + text; } else { text = "\"" + text; } if (attributes['rquote']) { text = text + attributes['rquote']; } else { text = text + "\""; } break; } } return text; } function updateBaseText(text) { text = text.trim(); text = text.replaceAll(String.fromCharCode(8290), ""); // invisible * text = text.replaceAll(String.fromCharCode(8292), ""); // invisible + text = text.replaceAll("\n", ""); text = text.replaceAll("\r", ""); return text; } let elements = []; let name = reader.GetNameNoNS(); switch (name) { case 'mi': case 'mo': case 'mn': case 'mspace': case 'mtext': case 'ms': let attributes = this.GetAttributes(reader); let text = updateBaseText(reader.GetText()); if (text.length === 0) break; text = decodeHexEntities(text); text = proceedAttributes({ name: name, text: text, attributes: attributes, oThis: this, parentMathContent: parentMathContent }); text = text.replaceAll(" ", " "); if (text) { let run = new AscWord.Run(null, true); run.AddText(text); this.proceedMathMLDefaultAttributes(attributes, elements, run, name); } break; case 'menclose': this.proceedMathMLDefaultAttributes( this.GetAttributes(reader), elements, this.handleBorderBox(reader) ); break; case 'mphantom': elements.push(this.handlePhantom(reader)); break; case 'msub': elements.push(this.handleDegree(reader, DEGREE_SUBSCRIPT)); break; case 'msup': elements.push(this.handleDegree(reader, DEGREE_SUPERSCRIPT)); break; case 'msubsup': elements.push(this.handleSubSup(reader, DEGREE_SubSup)); break; // case 'mmultiscripts': // { // let content = [] // let depth = reader.GetDepth(); // debugger // while (reader.ReadNextSiblingNode(depth)) // { // let current = AscWord.ParaMath.readMathMLContent(reader); // if (reader.GetNameNoNS() === 'mprescripts') // break; // content.push(current); // let post = AscWord.ParaMath.readMathMLContent(reader); // console.log(current, post) // } // console.log(content); // break; // } case 'munderover': let content = [] let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let current = this.readMathMLContent(reader) content.push(current); } let textFirstContent = content[0].GetTextOfElement().GetText().trim() if (AscMath.MathLiterals.nary.SearchU(textFirstContent)) { elements.push(this.handleSubSup(reader, DEGREE_SubSup, content)); } else { let Pr = new CMathLimitPr(); Pr.type = LIMIT_UP Pr.content = [ content[0], content[2], ]; var Limit = new AscMath.Limit(Pr); var MathContent = new AscCommonWord.CMathContent(); MathContent.addElementToContent(Limit); MathContent.Correct_Content(true) Pr.type = LIMIT_LOW Pr.content = [ MathContent, content[1] ]; let Limit2 = new CLimit(Pr); // if (munderoverAttributes['accent'] === 'true') // { // content[2].Apply_MenuProps({Action: c_oMathMenuAction.IncreaseArgumentSize}, 0); // } // else if (munderoverAttributes['accentunder'] === 'true') // { // content[1].Apply_MenuProps({Action: c_oMathMenuAction.IncreaseArgumentSize}, 0); // } elements.push(Limit2); } break; case 'mfrac': elements.push(this.handleFraction(reader)); break; case 'msqrt': this.proceedMathMLDefaultAttributes( this.GetAttributes(reader), elements, this.handleRadical(reader) ); break; case 'mroot': elements.push(this.handleRadical(reader, true)); break; case 'mpadded': case 'mstyle': case 'mrow': case 'merror': case 'semantics': elements = this.readMathMLMRow(reader); break; case 'munder': { let attributes = reader.GetAttributes(reader); if (attributes['displaystyle'] === 'true') { elements.push(this.handleNary(reader)); break; } if (attributes['accentunder'] === 'true') elements.push(this.handleAccent(reader, VJUST_TOP)); else elements.push(this.handleGroupChr(reader)); break; } case 'mover': { let attributes = this.GetAttributes(reader); if (attributes['accent'] === 'true') elements.push(this.handleAccent(reader, VJUST_BOT)); else elements.push(this.handleGroupChr(reader, VJUST_TOP)); break; } case 'mtable': elements.push(this.handleMatrix(reader)); break; // case 'mstack': // elements.push(AscMath.EqArray.fromMathML(reader, true)); // break; case 'mtr': case 'mlabeledtr': elements.push(this.handleMtr(reader)); break; case 'mtd': elements.push(this.handleMtd(reader)) break; case 'mfenced': elements.push(this.handleDelimiter(reader)); break; default: return elements; } return elements; }; this.readMathMLMRow = function(reader) { let result = [] let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { result = result.concat(this.readMathMLNode(reader)); } let processedResult = []; let i = 0; while (i < result.length) { let current = result[i]; let currentText = current instanceof ParaRun ? current.GetTextOfElement().GetText() : null; let isStretchy = !(current instanceof ParaRun && current.mathml_metadata && current.mathml_metadata.stretchy === false); if (currentText && isStretchy && (AscMath.MathLiterals.lBrackets.SearchU(currentText) || AscMath.MathLiterals.lrBrackets.SearchU(currentText))) { let openBracket = currentText; let bracketContent = []; let j = i + 1; let closeBracket = null; while (j < result.length) { let nextElement = result[j]; let nextText = nextElement instanceof ParaRun ? nextElement.GetTextOfElement().GetText() : null; let nextIsStretchy = !(nextElement instanceof ParaRun && nextElement.mathml_metadata && nextElement.mathml_metadata.stretchy === false); if (nextText && nextIsStretchy&& (AscMath.MathLiterals.rBrackets.SearchU(nextText) || AscMath.MathLiterals.lrBrackets.SearchU(nextText))) { closeBracket = nextText; break; } else { bracketContent.push(nextElement); } j++; } if (closeBracket) { processedResult.push(this.proceedMathMLImplicitDelimiter(bracketContent, openBracket, closeBracket)[0]); i = j + 1; } else { let remainingContent = result.slice(i + 1); processedResult.push(this.proceedMathMLImplicitDelimiter(remainingContent, openBracket, null)[0]); break; } } else if (currentText && isStretchy && AscMath.MathLiterals.rBrackets.SearchU(currentText)) { let precedingContent = []; let k = processedResult.length - 1; while (k >= 0) { let prevElement = processedResult[k]; if (prevElement instanceof AscMath.Delimiter) { break; } precedingContent.unshift(processedResult.pop()); k--; } if (precedingContent.length > 0) { processedResult.push(this.proceedMathMLImplicitDelimiter(precedingContent, null, currentText)[0]); } else { processedResult.push(current); } i++; } else if (current instanceof AscMath.Nary) { // N-ary operator found - check if it has empty content // If so, collect following inline elements as its argument let naryContent = current.getBase(); let isEmpty = !naryContent || naryContent.Content.length === 0; if (isEmpty && i + 1 < result.length) { let argContent = new AscCommonWord.CMathContent(); let j = i + 1; while (j < result.length) { let nextElement = result[j]; if (nextElement instanceof AscMath.Nary || nextElement instanceof AscMath.Delimiter || nextElement instanceof AscMath.Fraction || nextElement instanceof AscMath.Radical || nextElement instanceof AscMath.Matrix) { this.addFlatToMathContent(naryContent, nextElement); j++; break; } this.addFlatToMathContent(naryContent, nextElement); j++; } argContent.Correct_Content(true); processedResult.push(current); i = j; } else { processedResult.push(current); i++; } } else { processedResult.push(current); i++; } } if (processedResult.length >= 3) { for (let j = processedResult.length; j >= 0; j--) { let curent = processedResult[j-1]; let funcapply = processedResult[j-2]; let funcName = processedResult[j-3]; if (curent && funcName && funcapply && AscMath.MathLiterals.invisible.SearchU(funcapply.GetTextOfElement().GetText()) && funcName instanceof ParaRun) { let MathFunc = new CMathFunc({}); let MathContent = MathFunc.getFName(); MathContent.Add_Text(funcName.GetTextOfElement().GetText()); MathContent = MathFunc.getArgument(); MathContent.Add_Element(curent); processedResult.splice(j-3, j, MathFunc); break; } } } return processedResult; }; this.checkLinebreak = function (element) { for (let i = 0; i < this.mathMLData['mo-linebreak-todo'].length; i++) { let current = this.mathMLData['mo-linebreak-todo'][i]; let el = current.element; if (element === el) { this.mathMLData['mo-linebreak-todo'].splice(i, 1); let element = this.mathMLData['mo-id'][current.indenttarget]; return element.private_GetPosInParent(); // todo -> to operators } } return null; }; this.createContentFromText = function(text) { let mathContent = new CMathContent(); let run = new AscWord.Run(null, true); run.AddText(text); mathContent.addElementToContent(run); return mathContent; }; this.readMathMLContent = function(reader) { let mathContent = new CMathContent(); mathContent.mathml_metadata = {}; let elements = this.readMathMLNode(reader, mathContent); for (let i = 0; i < elements.length; ++i) { let current = elements[i]; this.addFlatToMathContent(mathContent, current); let breakPos = this.checkLinebreak(current) if (breakPos !== null) { current.Set_MathForcedBreak(true, undefined); } } mathContent.Correct_Content(true); return mathContent; }; this.readMathMLContentOnLevel = function (reader) { let content = []; let mathContent = new CMathContent(); let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { content = content.concat(this.readMathMLNode(reader)); } for (let i = 0; i < content.length; i++) { this.addFlatToMathContent(mathContent, content[i]); } if (content.length === 0) return; mathContent.Correct_Content(true); return mathContent; }; this.handleAccent = function(reader, type) { let props = new CMathAccentPr(); props.content = []; let isNary = false; function check(str) { for (let oIter = str.getUnicodeIterator(); oIter.check(); oIter.next()) { if (AscMath.MathLiterals.nary.SearchU(String.fromCodePoint(oIter.value()))) return true; } return false; } let mContents = []; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let current = this.readMathMLContent(reader); let base = current.GetTextOfElement().GetText(); if (mContents.length === 0 && check(base)) isNary = true; mContents.push(current); } if (isNary) { if (mContents.length > 2) { return this.handleSubSup(reader, mContents); } else { return this.handleDegree( reader, type === VJUST_TOP ? DEGREE_SUBSCRIPT : DEGREE_SUPERSCRIPT, mContents ); } } if (mContents.length >= 2) { props.content[0] = mContents[0]; if (mContents[1]) { let chrText = mContents[1].GetTextOfElement().GetText().trim(); if (chrText.length > 1 || !AscMath.MathLiterals.accent.SearchU(chrText)) { return this.handleGroupChr( reader, type === VJUST_BOT ? VJUST_TOP: VJUST_BOT, mContents ) } props.chr = chrText.charCodeAt(0); } } else { props.content[0] = mContents[0]; } return new CAccent(props); }; this.handleBorderBox = function(reader) { let props = new CMathBorderBoxPr(); props.content = []; let mathContent = new CMathContent(); props.content.push(mathContent); let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let elements = this.readMathMLNode(reader); for (let i = 0; i < elements.length; i++) { this.addFlatToMathContent(props.content[0], elements[i]); } } return new CBorderBox(props); }; this.handlePhantom = function (reader) { let props = new CMathPhantomPr(); props.show = false; props.transp = true; props.content = [this.readMathMLContentOnLevel(reader)]; return new CPhantom(props); }; this.handleDegree = function (reader, type, content) { let depth = reader.GetDepth(); let props = new CMathDegreePr(); props.type = undefined !== type ? type : DEGREE_SUPERSCRIPT; props.content = content ? content : []; while (reader.ReadNextSiblingNode(depth)) { let current = this.readMathMLContent(reader); props.content.push(current); } return new AscMath.Degree(props); }; this.handleSubSup = function (reader, type, content) { let depth = reader.GetDepth(); let props = new CMathDegreeSubSupPr(); props.type = undefined !== type ? type : DEGREE_PreSubSup; props.content = content ? content : []; if (!content) { while (reader.ReadNextSiblingNode(depth)) { let current = this.readMathMLContent(reader); props.content.push(current); } } let textOfBase = props.content[0].GetTextOfElement().GetText(); if (AscMath.MathLiterals.nary.SearchU(textOfBase)) { return this.handleNaryFromSubSup(props, false, false); } if (props.content.length === 3) { let temp = props.content[1]; props.content[1] = props.content[2]; props.content[2] = temp; } return new AscMath.DegreeSubSup(props); }; this.handleFraction = function (reader) { let attributes = this.GetAttributes(reader); let props = new CMathFractionPr(); props.content = []; if (attributes['bevelled']) props.type = SKEWED_FRACTION; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { props.content.push(this.readMathMLContent(reader)); } return new CFraction(props); }; this.handleLimit = function(reader, type, content) { let props = new CMathLimitPr(); props.content = content ? content : content; props.type = type; let mContents = []; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { mContents.push(this.readMathMLContent(reader)); } if (mContents.length) { if (mContents.length >= 2) { props.content[0] = mContents[0]; props.content[1] = mContents[1]; } else { props.content[0] = mContents[0]; } } return new CLimit(props); }; this.getGlobalAttributes = function(attributes, paraMath) { if (!paraMath) return; paraMath.mathml_metadata = {}; if (!attributes['display']) attributes['display'] = 'inline'; // default for mathMl if (attributes['display'] === 'block') paraMath.mathml_metadata['display'] = true; else paraMath.mathml_metadata['display'] = false; }; this.handleMathContent = function(reader, paraMath, mathContent) { let depth = reader.GetDepth(); this.getGlobalAttributes(this.GetAttributes(reader), paraMath); while (reader.ReadNextSiblingNode(depth)) { let elements = this.readMathMLNode(reader); for (let i = 0; i < elements.length; ++i) { let current = elements[i]; if (typeof current === 'string') continue; this.addFlatToMathContent(mathContent, current); let breakPos = this.checkLinebreak(current); if (breakPos !== null) current.Set_MathForcedBreak(true, undefined); } } }; this.normalizeMtableStructure = function(reader) { // Collect all rows, normalizing the structure mtable → mtr → mtd let normalizedRows = []; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let name = reader.GetNameNoNS(); if (name === "mtr" || name === "mlabeledtr") { // Normalize mtr content let nestedRows = []; let cells = this.normalizeMtrContent(reader, nestedRows); // If there are nested rows, add only them (parent row is empty) if (nestedRows.length > 0) { normalizedRows = normalizedRows.concat(nestedRows); } else { // No nested rows - add current row normalizedRows.push(cells); } } else if (name === "mtd") { // mtd directly in mtable - check for nested mtd let nestedCells = this.checkForNestedMtd(reader); if (nestedCells.length > 0) { // Has nested mtd - create row from them normalizedRows.push(nestedCells); } else { // No nested mtd - process as regular cell let cellContent = this.handleMtd(reader); if (cellContent) normalizedRows.push([cellContent]); } } else { // Any other element - wrap in mtr → mtd let elements = this.readMathMLNode(reader); if (elements.length > 0) { let mathContent = new CMathContent(); for (let i = 0; i < elements.length; i++) { mathContent.addElementToContent(elements[i]); } mathContent.Correct_Content(true); normalizedRows.push([mathContent]); } } } return normalizedRows; }; this.checkForNestedMtd = function(reader) { // Check if mtd contains nested mtd elements let cells = []; let depth = reader.GetDepth(); let hasNestedMtd = false; while (reader.ReadNextSiblingNode(depth)) { let name = reader.GetNameNoNS(); if (name === "mtd") { hasNestedMtd = true; let cellContent = this.handleMtd(reader); if (cellContent) cells.push(cellContent); } } // Return cells only if nested mtd were found return hasNestedMtd ? cells : []; }; this.normalizeMtrContent = function(reader, collectNestedRows) { // Process mtr content, ensuring all children are mtd let cells = []; let depth = reader.GetDepth(); let nonMtdElements = []; let nestedRows = collectNestedRows || []; while (reader.ReadNextSiblingNode(depth)) { let name = reader.GetNameNoNS(); if (name === "mtr" || name === "mlabeledtr") { // Nested mtr - incorrect structure // Collect nested rows to add at table level let nestedCells = this.normalizeMtrContent(reader, nestedRows); if (nestedCells.length > 0) { nestedRows.push(nestedCells); } } else if (name === "mtd") { // Valid mtd let cellContent = this.handleMtd(reader); // If non-mtd elements accumulated, wrap them in mtd if (nonMtdElements.length > 0) { let mathContent = new CMathContent(); for (let i = 0; i < nonMtdElements.length; i++) { this.addFlatToMathContent(mathContent, nonMtdElements[i]); } mathContent.Correct_Content(true); cells.push(mathContent); nonMtdElements = []; } if (cellContent) cells.push(cellContent); } else { // Non-mtd element inside mtr - accumulate for wrapping let elements = this.readMathMLNode(reader); nonMtdElements = nonMtdElements.concat(elements); } } // If non-mtd elements remain, wrap them in final cell if (nonMtdElements.length > 0) { let mathContent = new CMathContent(); for (let i = 0; i < nonMtdElements.length; i++) { this.addFlatToMathContent(mathContent, nonMtdElements[i]); } mathContent.Correct_Content(true); cells.push(mathContent); } // If no cells at all, create empty one if (cells.length === 0) { cells.push(new CMathContent()); } return cells; }; this.handleMatrix = function(reader) { let props = new CMathMatrixPr(); props.mrs = this.normalizeMtableStructure(reader); return new CMathMatrix(props); }; this.handleMtr = function(reader) { let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let name = reader.GetNameNoNS(); if (name === "mtd") return this.readMathMLContent(reader); } }; this.handleMtd = function(reader) { return this.readMathMLContentOnLevel(reader); }; this.handleEqArray = function(reader) { let props = new CMathEqArrPr(); props.content = []; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { let name = reader.GetNameNoNS(); if (name !== "msline") //skip msline for now props.content.push(this.readMathMLContent(reader)); } return new CEqArray(props); }; this.handleNary = function(reader, content, type) { let props = new CMathNaryPr(); props.content = content ? content : []; props.type = NARY_SubSup; props.supHide = type ? type.supHide : false; props.subHide = type ? type.subHide : false; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { props.content.push(this.readMathMLContent(reader)); } let firstContent = props.content.shift(); let charText = firstContent.GetTextOfElement().GetText(); props.chr = charText.charCodeAt(0); return this.private_fromMathML(props, true); }; this.handleNary_private = function(props, isAlredyChr) { if (!isAlredyChr) { let firstContent = props.content.shift(); let charText = firstContent.GetTextOfElement().GetText(); props.chr = charText.charCodeAt(0); } return new CNary(props); }; this.handleNaryFromSubSup = function(props, isSub, isSup) { let propsNary = new CMathNaryPr(); propsNary.limLoc = NARY_UndOvr; propsNary.supHide = isSub; propsNary.subHide = isSup; propsNary.content = props.content; if (props.content[0]) { if (props.content[0].mathml_metadata['movablelimits'] === 'true') propsNary.limLoc = NARY_SubSup; } let nary = this.handleNary_private(propsNary); return nary; }; this.handleDelimiter = function(reader, props) { if (!props) { let attributes = this.GetAttributes(reader); let open = attributes['open'] || "("; let close = attributes['close'] || ")"; let separator = attributes['separators'] || ","; props = new CMathDelimiterPr(); props.begChr = open.trim().charCodeAt(0); props.endChr = close.trim().charCodeAt(0); props.sepChr = separator[0].trim().charCodeAt(0); props.content = []; props.shp = DELIMITER_SHAPE_MATCH; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { props.content.push(this.readMathMLContent(reader)); } } return new CDelimiter(props); }; this.handleGroupChr = function(reader, type, content) { let props = new CMathGroupChrPr(); props.content = content ? content : []; props.pos = type; props.vertJc = (type === VJUST_TOP ) ? VJUST_BOT : undefined; let mContents = []; let depth = reader.GetDepth(); if (!content) { while (reader.ReadNextSiblingNode(depth)) { mContents.push(this.readMathMLContent(reader)); } } else { mContents = content; } if (mContents.length >= 2 || !content) { props.content.push(mContents[0]); if (mContents[1]) { let chrText = mContents[1].GetTextOfElement().GetText().trim(); if (chrText.length > 1 || !AscMath.MathLiterals.hbrack.SearchU(chrText)) return this.handleLimit(reader, type === 0 ? 1 : type, mContents); props.chr = chrText.charCodeAt(0); } } else if (!content) { props.content[0] = mContents[0]; } return new CGroupCharacter(props); } this.handleRadical = function(reader, isRoot) { let props = new CMathRadicalPr(); props.content = []; if (isRoot) { let mContents = []; let depth = reader.GetDepth(); while (reader.ReadNextSiblingNode(depth)) { mContents.push(this.readMathMLContent(reader)); } if (mContents.length === 2) { props.content[0] = mContents[1]; props.content[1] = mContents[0]; } else { let mathContent = new CMathContent(); for (let i = 0; i < mContents.length; i++) { let currentMathContent = mContents[i]; for (let j = 0; j < currentMathContent.Content.length; j++) { let curData = mContents } this.addFlatToMathContent(mathContent, mContents[i]); } mathContent.Correct_Content(true); props.content[1] = mathContent; } } else { let depth = reader.GetDepth(); let mathContent = new CMathContent(); while (reader.ReadNextSiblingNode(depth)) { let arrData = this.readMathMLNode(reader); for (let j = 0; j < arrData.length; j++) { this.addFlatToMathContent(mathContent, arrData[j]) } } mathContent.Correct_Content(true); props.content[1] = mathContent props.degHide = true; } return new CRadical(props); }; } //--------------------------------------------------------export---------------------------------------------------- AscMath.MathML = new MathML(); })();