1 | /* -----------------------------------------------------------------------------
|
2 | | Copyright (c) Jupyter Development Team.
|
3 | | Distributed under the terms of the Modified BSD License.
|
4 | |----------------------------------------------------------------------------*/
|
5 | // Some magic for deferring mathematical expressions to MathJax
|
6 | // by hiding them from the Markdown parser.
|
7 | // Some of the code here is adapted with permission from Davide Cervone
|
8 | // under the terms of the Apache2 license governing the MathJax project.
|
9 | // Other minor modifications are also due to StackExchange and are used with
|
10 | // permission.
|
11 | const inline = '$'; // the inline math delimiter
|
12 | // MATHSPLIT contains the pattern for math delimiters and special symbols
|
13 | // needed for searching for math in the text input.
|
14 | const MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[{}$]|[{}]|(?:\n\s*)+|@@\d+@@|\\\\(?:\(|\)|\[|\]))/i;
|
15 | /**
|
16 | * Break up the text into its component parts and search
|
17 | * through them for math delimiters, braces, linebreaks, etc.
|
18 | * Math delimiters must match and braces must balance.
|
19 | * Don't allow math to pass through a double linebreak
|
20 | * (which will be a paragraph).
|
21 | */
|
22 | export function removeMath(text) {
|
23 | const math = []; // stores math strings for later
|
24 | let start = null;
|
25 | let end = null;
|
26 | let last = null;
|
27 | let braces = 0;
|
28 | let deTilde;
|
29 | // Except for extreme edge cases, this should catch precisely those pieces of the markdown
|
30 | // source that will later be turned into code spans. While MathJax will not TeXify code spans,
|
31 | // we still have to consider them at this point; the following issue has happened several times:
|
32 | //
|
33 | // `$foo` and `$bar` are variables. --> <code>$foo ` and `$bar</code> are variables.
|
34 | const hasCodeSpans = text.includes('`') || text.includes('~~~');
|
35 | if (hasCodeSpans) {
|
36 | text = text
|
37 | .replace(/~/g, '~T')
|
38 | // note: the `fence` (three or more consecutive tildes or backticks)
|
39 | // can be followed by an `info string` but this cannot include backticks,
|
40 | // see specification: https://spec.commonmark.org/0.30/#info-string
|
41 | .replace(/^(?<fence>`{3,}|(~T){3,})[^`\n]*\n([\s\S]*?)^\k<fence>`*$/gm, wholematch => wholematch.replace(/\$/g, '~D'))
|
42 | .replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, wholematch => wholematch.replace(/\$/g, '~D'));
|
43 | deTilde = (text) => {
|
44 | return text.replace(/~([TD])/g, (wholematch, character) => character === 'T' ? '~' : inline);
|
45 | };
|
46 | }
|
47 | else {
|
48 | deTilde = (text) => {
|
49 | return text;
|
50 | };
|
51 | }
|
52 | let blocks = text.replace(/\r\n?/g, '\n').split(MATHSPLIT);
|
53 | for (let i = 1, m = blocks.length; i < m; i += 2) {
|
54 | const block = blocks[i];
|
55 | if (block.charAt(0) === '@') {
|
56 | //
|
57 | // Things that look like our math markers will get
|
58 | // stored and then retrieved along with the math.
|
59 | //
|
60 | blocks[i] = '@@' + math.length + '@@';
|
61 | math.push(block);
|
62 | }
|
63 | else if (start !== null) {
|
64 | //
|
65 | // If we are in math, look for the end delimiter,
|
66 | // but don't go past double line breaks, and
|
67 | // and balance braces within the math.
|
68 | //
|
69 | if (block === end) {
|
70 | if (braces) {
|
71 | last = i;
|
72 | }
|
73 | else {
|
74 | blocks = processMath(start, i, deTilde, math, blocks);
|
75 | start = null;
|
76 | end = null;
|
77 | last = null;
|
78 | }
|
79 | }
|
80 | else if (block.match(/\n.*\n/)) {
|
81 | if (last !== null) {
|
82 | i = last;
|
83 | blocks = processMath(start, i, deTilde, math, blocks);
|
84 | }
|
85 | start = null;
|
86 | end = null;
|
87 | last = null;
|
88 | braces = 0;
|
89 | }
|
90 | else if (block === '{') {
|
91 | braces++;
|
92 | }
|
93 | else if (block === '}' && braces) {
|
94 | braces--;
|
95 | }
|
96 | }
|
97 | else {
|
98 | //
|
99 | // Look for math start delimiters and when
|
100 | // found, set up the end delimiter.
|
101 | //
|
102 | if (block === inline || block === '$$') {
|
103 | start = i;
|
104 | end = block;
|
105 | braces = 0;
|
106 | }
|
107 | else if (block === '\\\\(' || block === '\\\\[') {
|
108 | start = i;
|
109 | end = block.slice(-1) === '(' ? '\\\\)' : '\\\\]';
|
110 | braces = 0;
|
111 | }
|
112 | else if (block.substr(1, 5) === 'begin') {
|
113 | start = i;
|
114 | end = '\\end' + block.substr(6);
|
115 | braces = 0;
|
116 | }
|
117 | }
|
118 | }
|
119 | if (start !== null && last !== null) {
|
120 | blocks = processMath(start, last, deTilde, math, blocks);
|
121 | start = null;
|
122 | end = null;
|
123 | last = null;
|
124 | }
|
125 | return { text: deTilde(blocks.join('')), math };
|
126 | }
|
127 | /**
|
128 | * Put back the math strings that were saved,
|
129 | * and clear the math array (no need to keep it around).
|
130 | */
|
131 | export function replaceMath(text, math) {
|
132 | /**
|
133 | * Replace a math placeholder with its corresponding group.
|
134 | * The math delimiters "\\(", "\\[", "\\)" and "\\]" are replaced
|
135 | * removing one backslash in order to be interpreted correctly by MathJax.
|
136 | */
|
137 | const process = (match, n) => {
|
138 | let group = math[n];
|
139 | if (group.substr(0, 3) === '\\\\(' &&
|
140 | group.substr(group.length - 3) === '\\\\)') {
|
141 | group = '\\(' + group.substring(3, group.length - 3) + '\\)';
|
142 | }
|
143 | else if (group.substr(0, 3) === '\\\\[' &&
|
144 | group.substr(group.length - 3) === '\\\\]') {
|
145 | group = '\\[' + group.substring(3, group.length - 3) + '\\]';
|
146 | }
|
147 | return group;
|
148 | };
|
149 | // Replace all the math group placeholders in the text
|
150 | // with the saved strings.
|
151 | return text.replace(/@@(\d+)@@/g, process);
|
152 | }
|
153 | /**
|
154 | * Process math blocks.
|
155 | *
|
156 | * The math is in blocks i through j, so
|
157 | * collect it into one block and clear the others.
|
158 | * Replace &, <, and > by named entities.
|
159 | * For IE, put <br> at the ends of comments since IE removes \n.
|
160 | * Clear the current math positions and store the index of the
|
161 | * math, then push the math string onto the storage array.
|
162 | * The preProcess function is called on all blocks if it has been passed in
|
163 | */
|
164 | function processMath(i, j, preProcess, math, blocks) {
|
165 | let block = blocks
|
166 | .slice(i, j + 1)
|
167 | .join('')
|
168 | .replace(/&/g, '&') // use HTML entity for &
|
169 | .replace(/</g, '<') // use HTML entity for <
|
170 | .replace(/>/g, '>'); // use HTML entity for >
|
171 | if (navigator && navigator.appName === 'Microsoft Internet Explorer') {
|
172 | block = block.replace(/(%[^\n]*)\n/g, '$1<br/>\n');
|
173 | }
|
174 | while (j > i) {
|
175 | blocks[j] = '';
|
176 | j--;
|
177 | }
|
178 | blocks[i] = '@@' + math.length + '@@'; // replace the current block text with a unique tag to find later
|
179 | if (preProcess) {
|
180 | block = preProcess(block);
|
181 | }
|
182 | math.push(block);
|
183 | return blocks;
|
184 | }
|
185 | //# sourceMappingURL=latex.js.map |
\ | No newline at end of file |