UNPKG

mathpix-markdown-it

Version:

Mathpix-markdown-it is an open source implementation of the mathpix-markdown spec written in Typescript. It relies on the following open source libraries: MathJax v3 (to render math with SVGs), markdown-it (for standard Markdown parsing)

449 lines 21.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.latex_footnotetext_block = exports.latex_footnote_block = void 0; var consts_1 = require("../common/consts"); var common_1 = require("../common"); var utils_1 = require("../utils"); var fence = require("markdown-it/lib/rules_block/fence.js"); // Symbol keys: collision-free on StateBlock; invisible to JSON.stringify/Object.keys (use `Object.getOwnPropertySymbols` to inspect). var FOOTNOTE_POS_KEY = Symbol('mmd.footnoteSrcPositions'); var FOOTNOTETEXT_POS_KEY = Symbol('mmd.footnotetextSrcPositions'); // Helper-owned /g sweeps. lastIndex is reset in the helper before each scan; sync-only. var FOOTNOTE_TOKEN_SWEEP_G = new RegExp(consts_1.reFootnoteToken.source, 'g'); var FOOTNOTETEXT_TOKEN_SWEEP_G = new RegExp(consts_1.reFootnotetextToken.source, 'g'); // Per-state cache of the last match offset (-1 if none). `patternG` MUST be /g. // Nested `state.md.block.parse(...)` builds a fresh StateBlock with its own src, so the Symbol-keyed cache on the outer state never leaks into nested parses. var getCachedSrcPositions = function (state, key, patternG) { var slot = state; var cached = slot[key]; // JS strings are immutable — identity check correctly invalidates on any reassignment of `state.src`. if (cached && cached.src === state.src) { return cached.lastPos; } patternG.lastIndex = 0; var lastPos = -1; var m; while ((m = patternG.exec(state.src)) !== null) { lastPos = m.index; // Empty-match guard for future regex edits. if (m.index === patternG.lastIndex) { patternG.lastIndex++; } } patternG.lastIndex = 0; slot[key] = { src: state.src, lastPos: lastPos }; return lastPos; }; var getTerminatorRulesForFootnotes = function (ruler) { var rules = ruler.__rules__; var arr = [ "table", "smilesDrawerBlock", "collapsible", "fence", "blockquote", "hr", "list", "footnote_def", "heading", "svg_block", "html_block", "pageBreaksBlock", "deflist", "BeginTable", "BeginAlign", "BeginTabular", "BeginProof", "BeginTheorem", "headingSection", "mathMLBlock", "pageBreaksBlock", "abstractBlock", "image_with_size_block" ]; var res = []; if (rules === null || rules === void 0 ? void 0 : rules.length) { for (var i = 0; i < rules.length; i++) { var rule = rules[i]; if (rule.enabled && arr.includes(rule.name)) { res.push(rule.fn); } } } return res; }; var latex_footnote_block = function (state, startLine, endLine, silent) { var _a, _b, _c, _d; try { var token = void 0, lineText = void 0, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; // Bail when the last `\footnote` literal is strictly before this block's start. Equality keeps the literal in scope (token starts on this block). `-1` from cache means no match anywhere. var lastFootnotePos = getCachedSrcPositions(state, FOOTNOTE_POS_KEY, FOOTNOTE_TOKEN_SWEEP_G); if (lastFootnotePos < state.bMarks[startLine]) { return false; } var nextLine = startLine + 1; var startPos = pos; var numbered = void 0; lineText = state.src.slice(pos, max); var fullContent = lineText; var hasOpenTag = false; var pending = ''; var terminate = false; // Literal token can't span `\n` — gate the O(fullContent) regex on per-line presence. var sawFootnoteToken = consts_1.reFootnoteToken.test(lineText); if (!sawFootnoteToken || !consts_1.reOpenTagFootnoteG.test(lineText)) { // Only `fence` terminates here; footnotetext uses full terminator list (pre-existing). for (; nextLine < endLine; nextLine++) { if (fence(state, nextLine, endLine, true)) { terminate = true; } if (terminate) { break; } if (state.isEmpty(nextLine)) { break; } pos = state.bMarks[nextLine]; max = state.eMarks[nextLine]; lineText = state.src.slice(pos, max); if (!lineText || !lineText.trim()) { break; } fullContent += fullContent ? '\n' : ''; fullContent += lineText; // Two cheap gates (token-seen, `{` present) both must hold before we run the heavy O(fullContent) regex; either gate's `continue` shortcuts to the next line. if (!sawFootnoteToken) { if (!consts_1.reFootnoteToken.test(lineText)) { continue; } sawFootnoteToken = true; } // Opening tag requires `{` — every alternative of the open-tag regex ends with literal `{`. // Lines without `{` cannot complete the pattern, so `continue` defers the heavy regex run // until a line with `{` arrives. Soundness: `{` cannot straddle a `\n` (it's a single char), // so if the pattern matches in `fullContent` after this iteration appended `lineText`, the // closing `{` must be present in `lineText` and the gate doesn't lose a match. if (!lineText.includes('{')) { continue; } if (consts_1.reOpenTagFootnoteG.test(fullContent)) { hasOpenTag = true; nextLine += 1; break; } } if (!hasOpenTag || nextLine > endLine) { return false; } } var dataTags = (0, utils_1.findOpenCloseTags)(fullContent, consts_1.reOpenTagFootnote, '', '', true); if (!((_a = dataTags === null || dataTags === void 0 ? void 0 : dataTags.arrOpen) === null || _a === void 0 ? void 0 : _a.length)) { return false; } pending = dataTags.pending; var matchNumbered = dataTags.arrOpen[dataTags.arrOpen.length - 1].content .match(consts_1.reOpenTagFootnoteNumbered); if (matchNumbered) { numbered = matchNumbered.groups.number; } var startFootnote = dataTags.arrOpen[dataTags.arrOpen.length - 1].posStart; var startContent = dataTags.arrOpen[dataTags.arrOpen.length - 1].posEnd; var content = fullContent.slice(startContent); var data = (0, common_1.findEndMarker)(content, -1, '{', '}', true); if (data === null || data === void 0 ? void 0 : data.res) { return false; } var hasEnd = false; var nextLineContent = nextLine; var inlineContentAfter = ''; var openBrackets = 0; var contentLength = content.length; for (; nextLine <= endLine; nextLine++) { if (fence(state, nextLine, endLine, true)) { terminate = true; } if (terminate) { break; } pos = state.bMarks[nextLine]; max = state.eMarks[nextLine]; lineText = state.src.slice(pos, max); if (hasEnd) { if (!lineText || !lineText.trim()) { break; } if (!(inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.length)) { nextLineContent = nextLine; } inlineContentAfter += '\n'; inlineContentAfter += lineText; var nextLineText = nextLine + 1 <= endLine ? state.src.slice(state.bMarks[nextLine + 1], state.eMarks[nextLine + 1]) : ''; if (!nextLineText || !nextLineText.trim()) { break; } continue; } fullContent += '\n'; fullContent += lineText; if (!lineText || !lineText.trim()) { pending = ''; } if (pending) { dataTags = (0, utils_1.findOpenCloseTags)(fullContent, consts_1.reOpenTagFootnotetext, ''); if (!((_b = dataTags === null || dataTags === void 0 ? void 0 : dataTags.arrOpen) === null || _b === void 0 ? void 0 : _b.length)) { break; } } data = (0, common_1.findEndMarker)(lineText, -1, '{', '}', true, openBrackets); if (data.res) { hasEnd = true; nextLineContent = nextLine; inlineContentAfter = state.src.slice(startPos + startContent + contentLength + 1 + data.nextPos, state.eMarks[nextLine]); content += '\n'; content += data.content; openBrackets = 0; continue; } else { if (data.openBrackets) { openBrackets = data.openBrackets; } } content += '\n'; content += lineText; contentLength = content ? content.length : 0; } if (!data || !data.res) { return false; } /** For validation mode we can terminate immediately */ if (silent) { return true; } state.line = nextLine + 1; var inlineContentBefore = startFootnote > 0 ? state.src.slice(startPos, startPos + startFootnote) : ''; token = state.push('paragraph_open', 'div', 1); token.map = [startLine, state.line]; if ((inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.length) && ((_c = inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.trim()) === null || _c === void 0 ? void 0 : _c.length)) { token = state.push('inline', '', 0); token.map = [startLine, startLine]; token.content = inlineContentBefore; token.bMarks = 0; token.eMarks = token.bMarks + token.content.length; token.bMarksContent = token.bMarks; token.eMarksContent = token.eMarks; token.lastBreakToSpace = true; token.children = []; } token = state.push('footnote_latex', '', 0); token.numbered = numbered; var children = []; state.md.block.parse(content, state.md, state.env, children); token.children = children; if ((inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.length) && ((_d = inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.trim()) === null || _d === void 0 ? void 0 : _d.length)) { token = state.push('inline', '', 0); token.map = [nextLineContent, nextLine + 1]; token.content = inlineContentAfter; token.firstBreakToSpace = true; token.children = []; } token = state.push('paragraph_close', 'div', -1); return true; } catch (e) { console.log("[ERROR]=>[latex_footnote_block]=>", e); return false; } }; exports.latex_footnote_block = latex_footnote_block; var latex_footnotetext_block = function (state, startLine, endLine, silent) { var _a, _b, _c, _d, _e, _f; try { var token = void 0, lineText = void 0, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; // Bail when the last `\footnotetext`/`\blfootnotetext` literal is strictly before this block's start. Equality keeps it in scope. `-1` means no match anywhere. var lastFootnotetextPos = getCachedSrcPositions(state, FOOTNOTETEXT_POS_KEY, FOOTNOTETEXT_TOKEN_SWEEP_G); if (lastFootnotetextPos < state.bMarks[startLine]) { return false; } var nextLine = startLine + 1; var startPos = pos; var numbered = void 0; lineText = state.src.slice(pos, max); var fullContent = lineText; var hasOpenTag = false; var pending = ''; var terminate = false; var terminatorRules = getTerminatorRulesForFootnotes(state.md.block.ruler); // Literal token can't span `\n` — gate the O(fullContent) regex on per-line presence. var sawFootnotetextToken = consts_1.reFootnotetextToken.test(lineText); if (!sawFootnotetextToken || !consts_1.reOpenTagFootnotetextG.test(lineText)) { // jump line-by-line until empty one or EOF for (; nextLine < endLine; nextLine++) { for (var i = 0; i < terminatorRules.length; i++) { if (terminatorRules[i](state, nextLine, endLine, true)) { terminate = true; break; } } if (terminate) { break; } if (state.isEmpty(nextLine)) { break; } pos = state.bMarks[nextLine]; max = state.eMarks[nextLine]; lineText = state.src.slice(pos, max); if (!lineText || !lineText.trim()) { break; } fullContent += fullContent ? '\n' : ''; fullContent += lineText; // Two cheap gates (token-seen, `{` present) both must hold before we run the heavy O(fullContent) regex; either gate's `continue` shortcuts to the next line. if (!sawFootnotetextToken) { if (!consts_1.reFootnotetextToken.test(lineText)) { continue; } sawFootnotetextToken = true; } // Opening tag requires `{` — skip lines without it (covers `\footnotetext\n…\n{` without rescanning fullContent). if (!lineText.includes('{')) { continue; } if (consts_1.reOpenTagFootnotetextG.test(fullContent)) { hasOpenTag = true; nextLine += 1; break; } } if (!hasOpenTag || nextLine > endLine) { return false; } } var dataTags = (0, utils_1.findOpenCloseTags)(fullContent, consts_1.reOpenTagFootnotetext, '', '', true); if (!((_a = dataTags === null || dataTags === void 0 ? void 0 : dataTags.arrOpen) === null || _a === void 0 ? void 0 : _a.length)) { return false; } pending = dataTags.pending; var openTag = dataTags.arrOpen[dataTags.arrOpen.length - 1].content; var matchNumbered = openTag .match(consts_1.reOpenTagFootnotetextNumbered); if (matchNumbered) { numbered = matchNumbered.groups.number; } var startFootnote = dataTags.arrOpen[dataTags.arrOpen.length - 1].posStart; var startContent = dataTags.arrOpen[dataTags.arrOpen.length - 1].posEnd; var content = fullContent.slice(startContent); var data = (0, common_1.findEndMarker)(content, -1, '{', '}', true); if (data === null || data === void 0 ? void 0 : data.res) { return false; } var hasEnd = false; var nextLineContent = nextLine; var inlineContentAfter = ''; var openBrackets = 0; var contentLength = content.length; var terminatedLine = -1; for (; nextLine <= endLine; nextLine++) { pos = state.bMarks[nextLine]; max = state.eMarks[nextLine]; lineText = state.src.slice(pos, max); if (hasEnd) { for (var i = 0; i < terminatorRules.length; i++) { if (terminatorRules[i](state, nextLine, endLine, true)) { terminatedLine = nextLine; terminate = true; break; } } if (terminate) { break; } if (!lineText || !lineText.trim()) { terminatedLine = nextLine; break; } if (!(inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.length)) { nextLineContent = nextLine; } inlineContentAfter += '\n'; inlineContentAfter += lineText; var nextLineText = nextLine + 1 <= endLine ? state.src.slice(state.bMarks[nextLine + 1], state.eMarks[nextLine + 1]) : ''; if (!nextLineText || !nextLineText.trim()) { break; } continue; } fullContent += '\n'; fullContent += lineText; if (!lineText || !lineText.trim()) { pending = ''; } if (pending) { dataTags = (0, utils_1.findOpenCloseTags)(fullContent, consts_1.reOpenTagFootnotetext, ''); if (!((_b = dataTags === null || dataTags === void 0 ? void 0 : dataTags.arrOpen) === null || _b === void 0 ? void 0 : _b.length)) { break; } } data = (0, common_1.findEndMarker)(lineText, -1, '{', '}', true, openBrackets); if (data.res) { hasEnd = true; nextLineContent = nextLine; inlineContentAfter = state.src.slice(startPos + startContent + contentLength + 1 + data.nextPos, state.eMarks[nextLine]); content += '\n'; content += data.content; openBrackets = 0; continue; } else { if (data.openBrackets) { openBrackets = data.openBrackets; } } content += '\n'; content += lineText; contentLength = content ? content.length : 0; } if (!data || !data.res) { return false; } /** For validation mode we can terminate immediately */ if (silent) { return true; } state.line = terminatedLine !== -1 ? nextLine : nextLine + 1; var inlineContentBefore = startFootnote > 0 ? state.src.slice(startPos, startPos + startFootnote) : ''; var needToCreateParagraph = ((inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.length) && ((_c = inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.trim()) === null || _c === void 0 ? void 0 : _c.length)) || ((inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.length) && ((_d = inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.trim()) === null || _d === void 0 ? void 0 : _d.length)); if (needToCreateParagraph) { token = state.push('paragraph_open', 'div', 1); token.map = [startLine, state.line]; } if ((inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.length) && ((_e = inlineContentBefore === null || inlineContentBefore === void 0 ? void 0 : inlineContentBefore.trim()) === null || _e === void 0 ? void 0 : _e.length)) { token = state.push('inline', '', 0); token.map = [startLine, startLine]; token.content = inlineContentBefore; token.bMarks = 0; token.eMarks = token.bMarks + token.content.length; token.bMarksContent = token.bMarks; token.eMarksContent = token.eMarks; token.lastBreakToSpace = true; token.children = []; } token = openTag.indexOf('blfootnotetext') === -1 ? state.push('footnotetext_latex', '', 0) : state.push('blfootnotetext_latex', '', 0); token.numbered = numbered; var children = []; state.md.block.parse(content, state.md, state.env, children); token.children = children; if ((inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.length) && ((_f = inlineContentAfter === null || inlineContentAfter === void 0 ? void 0 : inlineContentAfter.trim()) === null || _f === void 0 ? void 0 : _f.length)) { token = state.push('inline', '', 0); token.map = [nextLineContent, nextLine + 1]; token.content = inlineContentAfter; token.firstBreakToSpace = true; token.children = []; } if (needToCreateParagraph) { token = state.push('paragraph_close', 'div', -1); } return true; } catch (e) { console.log("[ERROR]=>[latex_footnotetext_block]=>", e); return false; } }; exports.latex_footnotetext_block = latex_footnotetext_block; //# sourceMappingURL=block-rule.js.map