From cfb9f2b31b9b2558a3d7173a90efb293ebe84a47 Mon Sep 17 00:00:00 2001 From: Emily Eisenberg Date: Tue, 6 Aug 2013 14:12:06 -0700 Subject: [PATCH] Refactor some stuff Summary: Pull node making into a separate module, make an "options" param for the make_ functions, and pull the different types of groups into separate functions. Test Plan: Open the homepage, make sure everything still works. Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D3368 --- buildTree.js | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++ katex.js | 260 +----------------------------------------- utils.js | 21 +++- 3 files changed, 342 insertions(+), 256 deletions(-) create mode 100644 buildTree.js diff --git a/buildTree.js b/buildTree.js new file mode 100644 index 000000000..a77f930ea --- /dev/null +++ b/buildTree.js @@ -0,0 +1,317 @@ +var Style = require("./Style"); + +var parseTree = require("./parseTree"); +var utils = require("./utils"); + +function Options(style, color) { + this.style = style; + this.color = color; +} + +Options.prototype.withStyle = function(style) { + return new Options(style, this.color); +} + +Options.prototype.withColor = function(color) { + return new Options(this.style, color); +} + +var buildExpression = function(expression, options, prev) { + var groups = []; + for (var i = 0; i < expression.length; i++) { + var group = expression[i]; + groups.push(buildGroup(group, options, prev)); + prev = group; + } + return groups; +}; + +var makeSpan = function(className, children) { + var span = document.createElement("span"); + span.className = className || ""; + + if (children) { + for (var i = 0; i < children.length; i++) { + span.appendChild(children[i]); + } + } + + return span; +}; + +var groupTypes = { + mathord: function(group, options, prev) { + return makeSpan("mord" + options.color, [mathit(group.value)]); + }, + + textord: function(group, options, prev) { + return makeSpan("mord" + options.color, [textit(group.value)]); + }, + + bin: function(group, options, prev) { + var className = "mbin"; + var prevAtom = prev; + while (prevAtom && prevAtom.type == "color") { + var atoms = prevAtom.value.value; + prevAtom = atoms[atoms.length - 1]; + } + if (!prev || utils.contains(["bin", "open", "rel"], prevAtom.type)) { + group.type = "ord"; + className = "mord"; + } + return makeSpan(className + options.color, [textit(group.value)]); + }, + + rel: function(group, options, prev) { + return makeSpan("mrel" + options.color, [textit(group.value)]); + }, + + sup: function(group, options, prev) { + var sup = makeSpan("msup " + options.style.cls(), [ + makeSpan(options.style.sup().cls(), [ + buildGroup(group.value.sup, + options.withStyle(options.style.sup())) + ]) + ]); + return makeSpan("mord", [ + buildGroup(group.value.base, options), sup + ]); + }, + + sub: function(group, options, prev) { + var sub = makeSpan("msub " + options.style.cls(), [ + makeSpan(options.style.sub().cls(), [ + buildGroup(group.value.sub, + options.withStyle(options.style.sub())) + ]) + ]); + return makeSpan("mord", [ + buildGroup(group.value.base, options), sub + ]); + }, + + supsub: function(group, options, prev) { + var sup = makeSpan("msup " + options.style.sup().cls(), [ + buildGroup(group.value.sup, options.withStyle(options.style.sup())) + ]); + var sub = makeSpan("msub " + options.style.sub().cls(), [ + buildGroup(group.value.sub, options.withStyle(options.style.sub())) + ]); + + var supsub = makeSpan("msupsub " + options.style.cls(), [sup, sub]); + + return makeSpan("mord", [ + buildGroup(group.value.base, options), supsub + ]); + }, + + open: function(group, options, prev) { + return makeSpan("mopen" + options.color, [textit(group.value)]); + }, + + close: function(group, options, prev) { + return makeSpan("mclose" + options.color, [textit(group.value)]); + }, + + frac: function(group, options, prev) { + if (utils.isBuggyWebKit) { + throw new ParseError( + "KaTeX fractions don't work in WebKit <= 537.1"); + } + + var fstyle = options.style; + if (group.value.size === "dfrac") { + fstyle = Style.DISPLAY; + } else if (group.value.size === "tfrac") { + fstyle = Style.TEXT; + } + + var nstyle = fstyle.fracNum(); + var dstyle = fstyle.fracDen(); + + var numer = makeSpan("mfracnum " + nstyle.cls(), [ + makeSpan("", [ + buildGroup(group.value.numer, options.withStyle(nstyle)) + ]) + ]); + var mid = makeSpan("mfracmid"); + var denom = makeSpan("mfracden " + dstyle.cls(), [ + makeSpan("", [ + buildGroup(group.value.denom, options.withStyle(dstyle)) + ]) + ]); + + return makeSpan("minner mfrac " + fstyle.cls() + options.color, [ + numer, mid, denom + ]); + }, + + color: function(group, options, prev) { + var frag = document.createDocumentFragment(); + var els = buildExpression( + group.value.value, + options.withColor(" " + group.value.color), + prev + ); + for (var i = 0; i < els.length; i++) { + frag.appendChild(els[i]); + } + return frag; + }, + + spacing: function(group, options, prev) { + if (group.value === "\\ " || group.value === "\\space") { + return makeSpan("mord mspace", [textit(group.value)]); + } else { + var spacingClassMap = { + "\\qquad": "qquad", + "\\quad": "quad", + "\\;": "thickspace", + "\\:": "mediumspace", + "\\,": "thinspace" + }; + + return makeSpan("mord mspace " + spacingClassMap[group.value]); + } + }, + + llap: function(group, options, prev) { + var inner = makeSpan("", [buildGroup(group.value, options)]); + return makeSpan("llap " + options.style.cls(), [inner]); + }, + + rlap: function(group, options, prev) { + var inner = makeSpan("", [buildGroup(group.value, options)]); + return makeSpan("rlap " + options.style.cls(), [inner]); + }, + + punct: function(group, options, prev) { + return makeSpan("mpunct" + options.color, [textit(group.value)]); + }, + + ordgroup: function(group, options, prev) { + return makeSpan("mord " + options.style.cls(), + buildExpression(group.value, options) + ); + }, + + namedfn: function(group, options, prev) { + return makeSpan("mop" + options.color, [textit(group.value.slice(1))]); + } +}; + +var buildGroup = function(group, options, prev) { + if (!group) { + return makeSpan(); + } + + if (groupTypes[group.type]) { + return groupTypes[group.type](group, options, prev); + } else { + throw new ParseError( + "Lex error: Got group of unknown type: '" + group.type + "'"); + } +}; + +var charLookup = { + "*": "\u2217", + "-": "\u2212", + "`": "\u2018", + "\\ ": "\u00a0", + "\\$": "$", + "\\angle": "\u2220", + "\\cdot": "\u22c5", + "\\circ": "\u2218", + "\\colon": ":", + "\\div": "\u00f7", + "\\geq": "\u2265", + "\\gets": "\u2190", + "\\infty": "\u221e", + "\\leftarrow": "\u2190", + "\\leq": "\u2264", + "\\lvert": "|", + "\\neq": "\u2260", + "\\ngeq": "\u2271", + "\\nleq": "\u2270", + "\\pm": "\u00b1", + "\\prime": "\u2032", + "\\rightarrow": "\u2192", + "\\rvert": "|", + "\\space": "\u00a0", + "\\times": "\u00d7", + "\\to": "\u2192", + + "\\alpha": "\u03b1", + "\\beta": "\u03b2", + "\\gamma": "\u03b3", + "\\delta": "\u03b4", + "\\epsilon": "\u03f5", + "\\zeta": "\u03b6", + "\\eta": "\u03b7", + "\\theta": "\u03b8", + "\\iota": "\u03b9", + "\\kappa": "\u03ba", + "\\lambda": "\u03bb", + "\\mu": "\u03bc", + "\\nu": "\u03bd", + "\\xi": "\u03be", + "\\omicron": "\u03bf", + "\\pi": "\u03c0", + "\\rho": "\u03c1", + "\\sigma": "\u03c3", + "\\tau": "\u03c4", + "\\upsilon": "\u03c5", + "\\phi": "\u03d5", + "\\chi": "\u03c7", + "\\psi": "\u03c8", + "\\omega": "\u03c9", + "\\varepsilon": "\u03b5", + "\\vartheta": "\u03d1", + "\\varpi": "\u03d6", + "\\varrho": "\u03f1", + "\\varsigma": "\u03c2", + "\\varphi": "\u03c6", + + "\\Gamma": "\u0393", + "\\Delta": "\u0394", + "\\Theta": "\u0398", + "\\Lambda": "\u039b", + "\\Xi": "\u039e", + "\\Pi": "\u03a0", + "\\Sigma": "\u03a3", + "\\Upsilon": "\u03a5", + "\\Phi": "\u03a6", + "\\Psi": "\u03a8", + "\\Omega": "\u03a9" +}; + +var textit = function(value) { + if (value in charLookup) { + value = charLookup[value]; + } + return document.createTextNode(value); +}; + +var mathit = function(value) { + return makeSpan("mathit", [textit(value)]); +}; + +var clearNode = function(node) { + if ("textContent" in node) { + node.textContent = ""; + } else { + node.innerText = ""; + } +}; + +var buildTree = function(tree) { + var options = new Options(Style.TEXT, ""); + + var expression = buildExpression(tree, options); + var span = makeSpan(options.style.cls(), expression); + var katexNode = makeSpan("katex", [span]); + + return katexNode; +}; + +module.exports = buildTree; diff --git a/katex.js b/katex.js index 46a5f1edf..1b35eb334 100644 --- a/katex.js +++ b/katex.js @@ -1,264 +1,14 @@ -var Style = require("./Style"); -var ParseError = require("./ParseError"); - var parseTree = require("./parseTree"); +var buildTree = require("./buildTree"); var utils = require("./utils"); - -var buildExpression = function(style, color, expression, prev) { - var groups = []; - for (var i = 0; i < expression.length; i++) { - var group = expression[i]; - groups.push(buildGroup(style, color, group, prev)); - prev = group; - } - return groups; -}; - -var makeSpan = function(className, children) { - var span = document.createElement("span"); - span.className = className || ""; - - if (children) { - for (var i = 0; i < children.length; i++) { - span.appendChild(children[i]); - } - } - - return span; -}; - -var buildGroup = function(style, color, group, prev) { - if (!group) { - return makeSpan(); - } - - if (group.type === "mathord") { - return makeSpan("mord" + color, [mathit(group.value)]); - } else if (group.type === "textord") { - return makeSpan("mord" + color, [textit(group.value)]); - } else if (group.type === "bin") { - var className = "mbin"; - var prevAtom = prev; - while (prevAtom && prevAtom.type == "color") { - var atoms = prevAtom.value.value; - prevAtom = atoms[atoms.length - 1]; - } - if (!prev || utils.contains(["bin", "open", "rel"], prevAtom.type)) { - group.type = "ord"; - className = "mord"; - } - return makeSpan(className + color, [textit(group.value)]); - } else if (group.type === "rel") { - return makeSpan("mrel" + color, [textit(group.value)]); - } else if (group.type === "sup") { - var sup = makeSpan("msup " + style.cls(), [ - makeSpan(style.sup().cls(), [ - buildGroup(style.sup(), color, group.value.sup) - ]) - ]); - return makeSpan("mord", [ - buildGroup(style, color, group.value.base), sup - ]); - } else if (group.type === "sub") { - var sub = makeSpan("msub " + style.cls(), [ - makeSpan(style.sub().cls(), [ - buildGroup(style.sub(), color, group.value.sub) - ]) - ]); - return makeSpan("mord", [ - buildGroup(style, color, group.value.base), sub - ]); - } else if (group.type === "supsub") { - var sup = makeSpan("msup " + style.sup().cls(), [ - buildGroup(style.sup(), color, group.value.sup) - ]); - var sub = makeSpan("msub " + style.sub().cls(), [ - buildGroup(style.sub(), color, group.value.sub) - ]); - - var supsub = makeSpan("msupsub " + style.cls(), [sup, sub]); - - return makeSpan("mord", [ - buildGroup(style, color, group.value.base), supsub - ]); - } else if (group.type === "open") { - return makeSpan("mopen" + color, [textit(group.value)]); - } else if (group.type === "close") { - return makeSpan("mclose" + color, [textit(group.value)]); - } else if (group.type === "frac") { - if (utils.isBuggyWebKit) { - throw new ParseError( - "KaTeX fractions don't work in WebKit <= 537.1"); - } - - var fstyle = style; - if (group.value.size === "dfrac") { - fstyle = Style.DISPLAY; - } else if (group.value.size === "tfrac") { - fstyle = Style.TEXT; - } - - var nstyle = fstyle.fracNum(); - var dstyle = fstyle.fracDen(); - - var numer = makeSpan("mfracnum " + nstyle.cls(), [ - makeSpan("", [buildGroup(nstyle, color, group.value.numer)]) - ]); - var mid = makeSpan("mfracmid"); - var denom = makeSpan("mfracden " + dstyle.cls(), [ - makeSpan("", [buildGroup(dstyle, color, group.value.denom)]) - ]); - - return makeSpan("minner mfrac " + fstyle.cls() + color, [ - numer, mid, denom - ]); - } else if (group.type === "color") { - var frag = document.createDocumentFragment(); - var els = buildExpression( - style, - " " + group.value.color, - group.value.value, - prev - ); - for (var i = 0; i < els.length; i++) { - frag.appendChild(els[i]); - } - return frag; - } else if (group.type === "spacing") { - if (group.value === "\\ " || group.value === "\\space") { - return makeSpan("mord mspace", [textit(group.value)]); - } else { - var spacingClassMap = { - "\\qquad": "qquad", - "\\quad": "quad", - "\\;": "thickspace", - "\\:": "mediumspace", - "\\,": "thinspace" - }; - - return makeSpan("mord mspace " + spacingClassMap[group.value]); - } - } else if (group.type === "llap") { - var inner = makeSpan("", [buildGroup(style, color, group.value)]); - return makeSpan("llap " + style.cls(), [inner]); - } else if (group.type === "rlap") { - var inner = makeSpan("", [buildGroup(style, color, group.value)]); - return makeSpan("rlap " + style.cls(), [inner]); - } else if (group.type === "punct") { - return makeSpan("mpunct" + color, [textit(group.value)]); - } else if (group.type === "ordgroup") { - return makeSpan("mord " + style.cls(), - buildExpression(style, color, group.value) - ); - } else if (group.type === "namedfn") { - return makeSpan("mop" + color, [textit(group.value.slice(1))]); - } else { - throw new ParseError( - "Lex error: Got group of unknown type: '" + group.type + "'"); - } -}; - -var charLookup = { - "*": "\u2217", - "-": "\u2212", - "`": "\u2018", - "\\ ": "\u00a0", - "\\$": "$", - "\\angle": "\u2220", - "\\cdot": "\u22c5", - "\\circ": "\u2218", - "\\colon": ":", - "\\div": "\u00f7", - "\\geq": "\u2265", - "\\gets": "\u2190", - "\\infty": "\u221e", - "\\leftarrow": "\u2190", - "\\leq": "\u2264", - "\\lvert": "|", - "\\neq": "\u2260", - "\\ngeq": "\u2271", - "\\nleq": "\u2270", - "\\pm": "\u00b1", - "\\prime": "\u2032", - "\\rightarrow": "\u2192", - "\\rvert": "|", - "\\space": "\u00a0", - "\\times": "\u00d7", - "\\to": "\u2192", - - "\\alpha": "\u03b1", - "\\beta": "\u03b2", - "\\gamma": "\u03b3", - "\\delta": "\u03b4", - "\\epsilon": "\u03f5", - "\\zeta": "\u03b6", - "\\eta": "\u03b7", - "\\theta": "\u03b8", - "\\iota": "\u03b9", - "\\kappa": "\u03ba", - "\\lambda": "\u03bb", - "\\mu": "\u03bc", - "\\nu": "\u03bd", - "\\xi": "\u03be", - "\\omicron": "\u03bf", - "\\pi": "\u03c0", - "\\rho": "\u03c1", - "\\sigma": "\u03c3", - "\\tau": "\u03c4", - "\\upsilon": "\u03c5", - "\\phi": "\u03d5", - "\\chi": "\u03c7", - "\\psi": "\u03c8", - "\\omega": "\u03c9", - "\\varepsilon": "\u03b5", - "\\vartheta": "\u03d1", - "\\varpi": "\u03d6", - "\\varrho": "\u03f1", - "\\varsigma": "\u03c2", - "\\varphi": "\u03c6", - - "\\Gamma": "\u0393", - "\\Delta": "\u0394", - "\\Theta": "\u0398", - "\\Lambda": "\u039b", - "\\Xi": "\u039e", - "\\Pi": "\u03a0", - "\\Sigma": "\u03a3", - "\\Upsilon": "\u03a5", - "\\Phi": "\u03a6", - "\\Psi": "\u03a8", - "\\Omega": "\u03a9" -}; - -var textit = function(value) { - if (value in charLookup) { - value = charLookup[value]; - } - return document.createTextNode(value); -}; - -var mathit = function(value) { - return makeSpan("mathit", [textit(value)]); -}; - -var clearNode = function(node) { - if ("textContent" in node) { - node.textContent = ""; - } else { - node.innerText = ""; - } -}; +var ParseError = require("./ParseError"); var process = function(toParse, baseNode) { var tree = parseTree(toParse); + var node = buildTree(tree); - var style = Style.TEXT; - var expression = buildExpression(style, /* color: */ "", tree); - var span = makeSpan(style.cls(), expression); - var katexNode = makeSpan("katex", [span]); - - clearNode(baseNode); - baseNode.appendChild(katexNode); + utils.clearNode(baseNode); + baseNode.appendChild(node); }; module.exports = { diff --git a/utils.js b/utils.js index e7feae9a5..db9863488 100644 --- a/utils.js +++ b/utils.js @@ -43,7 +43,26 @@ function isBuggyWebKit() { return major < 537 || (major == 537 && minor <= 1); } +var setTextContent; + +var testNode = document.createElement("span"); +if ("textContent" in testNode) { + setTextContent = function(node, text) { + node.textContent = text; + }; +} else { + setTextContent = function(node, text) { + node.innerText = text; + }; +} + +function clearNode(node) { + setTextContent(node, ""); +} + module.exports = { contains: contains, - isBuggyWebKit: isBuggyWebKit() + isBuggyWebKit: isBuggyWebKit(), + setTextContent: setTextContent, + clearNode: clearNode };