Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

ENH: added StackExchange-style MathJax filtering

  • Loading branch information...
commit 58121491d37da1ba9d1525f08575b729c6905eb6 1 parent a0ab74e
@ahmadia authored
Showing with 267 additions and 0 deletions.
  1. +267 −0 IPython/frontend/html/notebook/static/js/textcell.js
View
267 IPython/frontend/html/notebook/static/js/textcell.js
@@ -6,6 +6,128 @@
//----------------------------------------------------------------------------
//============================================================================
+// Cross-browser RegEx Split
+//============================================================================
+
+// see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
+/*!
+ * Cross-Browser Split 1.1.1
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
+ * Available under the MIT License
+ * ECMAScript compliant, uniform cross-browser split method
+ */
+
+/**
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
+ * separator are not included in the result array. However, if `separator` is a regex that contains
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
+ * cross-browser.
+ * @param {String} str String to split.
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
+ * @param {Number} [limit] Maximum number of items to include in the result array.
+ * @returns {Array} Array of substrings.
+ * @example
+ *
+ * // Basic use
+ * split('a b c d', ' ');
+ * // -> ['a', 'b', 'c', 'd']
+ *
+ * // With limit
+ * split('a b c d', ' ', 2);
+ * // -> ['a', 'b']
+ *
+ * // Backreferences in result array
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
+ */
+var split;
+
+// Avoid running twice; that would break the `nativeSplit` reference
+split = split || function (undef) {
+
+ var nativeSplit = String.prototype.split,
+ compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
+ self;
+
+ self = function (str, separator, limit) {
+ // If `separator` is not a regex, use `nativeSplit`
+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
+ return nativeSplit.call(str, separator, limit);
+ }
+ var output = [],
+ flags = (separator.ignoreCase ? "i" : "") +
+ (separator.multiline ? "m" : "") +
+ (separator.extended ? "x" : "") + // Proposed for ES6
+ (separator.sticky ? "y" : ""), // Firefox 3+
+ lastLastIndex = 0,
+ // Make `global` and avoid `lastIndex` issues by working with a copy
+ separator = new RegExp(separator.source, flags + "g"),
+ separator2, match, lastIndex, lastLength;
+ str += ""; // Type-convert
+ if (!compliantExecNpcg) {
+ // Doesn't need flags gy, but they don't hurt
+ separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
+ }
+ /* Values for `limit`, per the spec:
+ * If undefined: 4294967295 // Math.pow(2, 32) - 1
+ * If 0, Infinity, or NaN: 0
+ * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
+ * If negative number: 4294967296 - Math.floor(Math.abs(limit))
+ * If other: Type-convert, then use the above rules
+ */
+ limit = limit === undef ?
+ -1 >>> 0 : // Math.pow(2, 32) - 1
+ limit >>> 0; // ToUint32(limit)
+ while (match = separator.exec(str)) {
+ // `separator.lastIndex` is not reliable cross-browser
+ lastIndex = match.index + match[0].length;
+ if (lastIndex > lastLastIndex) {
+ output.push(str.slice(lastLastIndex, match.index));
+ // Fix browsers whose `exec` methods don't consistently return `undefined` for
+ // nonparticipating capturing groups
+ if (!compliantExecNpcg && match.length > 1) {
+ match[0].replace(separator2, function () {
+ for (var i = 1; i < arguments.length - 2; i++) {
+ if (arguments[i] === undef) {
+ match[i] = undef;
+ }
+ }
+ });
+ }
+ if (match.length > 1 && match.index < str.length) {
+ Array.prototype.push.apply(output, match.slice(1));
+ }
+ lastLength = match[0].length;
+ lastLastIndex = lastIndex;
+ if (output.length >= limit) {
+ break;
+ }
+ }
+ if (separator.lastIndex === match.index) {
+ separator.lastIndex++; // Avoid an infinite loop
+ }
+ }
+ if (lastLastIndex === str.length) {
+ if (lastLength || !separator.test("")) {
+ output.push("");
+ }
+ } else {
+ output.push(str.slice(lastLastIndex));
+ }
+ return output.length > limit ? output.slice(0, limit) : output;
+ };
+
+ // For convenience
+ String.prototype.split = function (separator, limit) {
+ return self(this, separator, limit);
+ };
+
+ return self;
+
+}();
+
+//============================================================================
// TextCell
//============================================================================
@@ -201,6 +323,147 @@ var IPython = (function (IPython) {
// MarkdownCell
+ // Some magic for deferring mathematical expressions to MathJaX
+ // Some of the logic here is reused with permission from Stack Exchange Inc.
+
+ var inline = "$"; // the inline math delimiter
+ var blocks, start, end, last, braces; // used in searching for math
+ var math; // stores math until pagedown (Markdown parser) is done
+ var HUB = MathJax.Hub;
+
+ // MATHSPLIT contains the pattern for math delimiters and special symbols
+ // needed for searching for math in the text input.
+ var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
+
+ // The math is in blocks i through j, so
+ // collect it into one block and clear the others.
+ // Replace &, <, and > by named entities.
+ // For IE, put <br> at the ends of comments since IE removes \n.
+ // Clear the current math positions and store the index of the
+ // math, then push the math string onto the storage array.
+ // The preProcess function is called on all blocks if it has been passed in
+ function processMath(i, j, preProcess) {
+ var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
+ .replace(/</g, "&lt;") // use HTML entity for <
+ .replace(/>/g, "&gt;") // use HTML entity for >
+ ;
+ if (HUB.Browser.isMSIE) {
+ block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")
+ }
+ while (j > i) {
+ blocks[j] = "";
+ j--;
+ }
+ blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
+ if (preProcess)
+ block = preProcess(block);
+ math.push(block);
+ start = end = last = null;
+ }
+
+ // Break up the text into its component parts and search
+ // through them for math delimiters, braces, linebreaks, etc.
+ // Math delimiters must match and braces must balance.
+ // Don't allow math to pass through a double linebreak
+ // (which will be a paragraph).
+ //
+ function removeMath(text) {
+ start = end = last = null; // for tracking math delimiters
+ math = []; // stores math strings for later
+
+ // Except for extreme edge cases, this should catch precisely those pieces of the markdown
+ // source that will later be turned into code spans. While MathJax will not TeXify code spans,
+ // we still have to consider them at this point; the following issue has happened several times:
+ //
+ // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
+
+ var hasCodeSpans = /`/.test(text),
+ deTilde;
+ if (hasCodeSpans) {
+ text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
+ return wholematch.replace(/\$/g, "~D");
+ });
+ deTilde = function (text) { return text.replace(/~([TD])/g, function (wholematch, character) { return { T: "~", D: "$" }[character]; }) };
+ } else {
+ deTilde = function (text) { return text; };
+ }
+
+ blocks = text.replace(/\r\n?/g, "\n").split(MATHSPLIT);
+
+ for (var i = 1, m = blocks.length; i < m; i += 2) {
+ var block = blocks[i];
+ if (block.charAt(0) === "@") {
+ //
+ // Things that look like our math markers will get
+ // stored and then retrieved along with the math.
+ //
+ blocks[i] = "@@" + math.length + "@@";
+ math.push(block);
+ }
+ else if (start) {
+ //
+ // If we are in math, look for the end delimiter,
+ // but don't go past double line breaks, and
+ // and balance braces within the math.
+ //
+ if (block === end) {
+ if (braces) {
+ last = i
+ }
+ else {
+ processMath(start, i, deTilde)
+ }
+ }
+ else if (block.match(/\n.*\n/)) {
+ if (last) {
+ i = last;
+ processMath(start, i, deTilde)
+ }
+ start = end = last = null;
+ braces = 0;
+ }
+ else if (block === "{") {
+ braces++
+ }
+ else if (block === "}" && braces) {
+ braces--
+ }
+ }
+ else {
+ //
+ // Look for math start delimiters and when
+ // found, set up the end delimiter.
+ //
+ if (block === inline || block === "$$") {
+ start = i;
+ end = block;
+ braces = 0;
+ }
+ else if (block.substr(1, 5) === "begin") {
+ start = i;
+ end = "\\end" + block.substr(6);
+ braces = 0;
+ }
+ }
+ }
+ if (last) {
+ processMath(start, last, deTilde)
+ }
+ return deTilde(blocks.join(""));
+ }
+
+ //
+ // Put back the math strings that were saved,
+ // and clear the math array (no need to keep it around).
+ //
+ function replaceMath(text) {
+ text = text.replace(/@@(\d+)@@/g, function (match, n) {
+ return math[n]
+ });
+ math = null;
+ return text;
+ }
+
var MarkdownCell = function (notebook) {
this.placeholder = "Type *Markdown* and LaTeX: $\\alpha^2$";
IPython.TextCell.apply(this, arguments);
@@ -215,7 +478,11 @@ var IPython = (function (IPython) {
if (this.rendered === false) {
var text = this.get_text();
if (text === "") { text = this.placeholder; }
+
+ text = removeMath(text)
var html = IPython.markdown_converter.makeHtml(text);
+ html = replaceMath(html)
+
this.set_rendered(html);
this.typeset()
this.element.find('div.text_cell_input').hide();
Please sign in to comment.
Something went wrong with that request. Please try again.