Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/util/mathjax-utils.js
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45// This is taken from Jupyter, which is BSD/Apache2 licensed... -- https://github.com/jupyter/notebook/blob/master/notebook/static/notebook/js/mathjaxutils.js67// Some magic for deferring mathematical expressions to MathJax8// by hiding them from the Markdown parser.9// Some of the code here is adapted with permission from Davide Cervone10// under the terms of the Apache2 license governing the MathJax project.11// Other minor modifications are also due to StackExchange and are used with12// permission.1314// MATHSPLIT contains the pattern for math delimiters and special symbols15// needed for searching for math in the text input.1617const MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|(?:\n\s*)+)/i;1819// This would also capture \[ \] \( \), but I don't want to do that because20// Jupyter classic doesn't and it conflicts too much with markdown. Use $'s and e.g., \begin{equation}.21// const MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|(?:\n\s*)+|\\(?:\(|\)|\[|\]))/i;2223// This runs under node.js and is js (not ts) so can't use import.24const { regex_split } = require("./regex-split");2526// The math is in blocks i through j, so27// collect it into one block and clear the others.28// Clear the current math positions and store the index of the29// math, then push the math string onto the storage array.30// The preProcess function is called on all blocks if it has been passed in31function process_math(i, j, pre_process, math, blocks, tags) {32let block = blocks.slice(i, j + 1).join("");33while (j > i) {34blocks[j] = "";35j--;36}37// replace the current block text with a unique tag to find later38if (block[0] === "$" && block[1] === "$") {39blocks[i] = tags.display_open + math.length + tags.display_close;40} else {41blocks[i] = tags.open + math.length + tags.close;42}43if (pre_process) {44block = pre_process(block);45}46math.push(block);47return blocks;48}4950// Break up the text into its component parts and search51// through them for math delimiters, braces, linebreaks, etc.52// Math delimiters must match and braces must balance.53// Don't allow math to pass through a double linebreak54// (which will be a paragraph).55//5657// Do *NOT* conflict with the ones used in ./markdown-utils.ts58const MATH_ESCAPE = "\uFE32\uFE33"; // unused unicode -- hardcoded below too59exports.MATH_ESCAPE = MATH_ESCAPE;6061const DEFAULT_TAGS = {62open: MATH_ESCAPE,63close: MATH_ESCAPE,64display_open: MATH_ESCAPE,65display_close: MATH_ESCAPE,66};6768function remove_math(text, tags = DEFAULT_TAGS) {69let math = []; // stores math strings for later70let start;71let end;72let last;73let braces;7475// Except for extreme edge cases, this should catch precisely those pieces of the markdown76// source that will later be turned into code spans. While MathJax will not TeXify code spans,77// we still have to consider them at this point; the following issue has happened several times:78//79// `$foo` and `$bar` are variables. --> <code>$foo ` and `$bar</code> are variables.8081let hasCodeSpans = /`/.test(text),82de_tilde;83if (hasCodeSpans) {84text = text85.replace(/~/g, "~T")86.replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {87return wholematch.replace(/\$/g, "~D");88});89de_tilde = function (text) {90return text.replace(/~([TD])/g, function (wholematch, character) {91return { T: "~", D: "$" }[character];92});93};94} else {95de_tilde = function (text) {96return text;97};98}99100let blocks = regex_split(text.replace(/\r\n?/g, "\n"), MATHSPLIT);101102for (let i = 1, m = blocks.length; i < m; i += 2) {103const block = blocks[i];104if (start) {105//106// If we are in math, look for the end delimiter,107// but don't go past double line breaks, and108// and balance braces within the math.109//110if (block === end) {111if (braces) {112last = i;113} else {114blocks = process_math(start, i, de_tilde, math, blocks, tags);115start = null;116end = null;117last = null;118}119} else if (block.match(/\n.*\n/)) {120if (last) {121i = last;122blocks = process_math(start, i, de_tilde, math, blocks, tags);123}124start = null;125end = null;126last = null;127braces = 0;128} else if (block === "{") {129braces++;130} else if (block === "}" && braces) {131braces--;132}133} else {134//135// Look for math start delimiters and when136// found, set up the end delimiter.137//138if (block === "$" || block === "$$") {139start = i;140end = block;141braces = 0;142} else if (block === "\\\\(" || block === "\\\\[") {143start = i;144end = block.slice(-1) === "(" ? "\\\\)" : "\\\\]";145braces = 0;146} else if (block.substr(1, 5) === "begin") {147start = i;148end = "\\end" + block.substr(6);149braces = 0;150}151}152}153if (last) {154blocks = process_math(start, last, de_tilde, math, blocks, tags);155start = null;156end = null;157last = null;158}159return [de_tilde(blocks.join("")), math];160}161exports.remove_math = remove_math;162163//164// Put back the math strings that were saved.165//166function replace_math(text, math, tags) {167// Replace all the math group placeholders in the text168// with the saved strings.169if (tags == null) {170// Easy to do with a regexp171return text.replace(/\uFE32\uFE33(\d+)\uFE32\uFE33/g, function (match, n) {172return math[n];173});174} else {175// harder since tags could be anything.176// We assume that tags.display_open doesn't match tags.open and similarly with close,177// e.g., the display one is more complicated.178// .split might be faster...?179while (true) {180const i = text.indexOf(tags.display_open);181if (i == -1) break;182const j = text.indexOf(tags.display_close);183if (j == -1) break;184const n = parseInt(text.slice(i + tags.display_open.length, j));185text =186text.slice(0, i) + math[n] + text.slice(j + tags.display_close.length);187}188while (true) {189const i = text.indexOf(tags.open);190if (i == -1) break;191const j = text.indexOf(tags.close);192if (j == -1) break;193const n = parseInt(text.slice(i + tags.open.length, j));194text = text.slice(0, i) + math[n] + text.slice(j + tags.close.length);195}196return text;197}198}199200exports.replace_math = replace_math;201202203