1<script type="text/javascript">
2/* global katex */
3
4var findEndOfMath = function(delimiter, text, startIndex) {
5    // Adapted from
6    // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
7    var index = startIndex;
8    var braceLevel = 0;
9
10    var delimLength = delimiter.length;
11
12    while (index < text.length) {
13        var character = text[index];
14
15        if (braceLevel <= 0 &&
16            text.slice(index, index + delimLength) === delimiter) {
17            return index;
18        } else if (character === "\\") {
19            index++;
20        } else if (character === "{") {
21            braceLevel++;
22        } else if (character === "}") {
23            braceLevel--;
24        }
25
26        index++;
27    }
28
29    return -1;
30};
31
32var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
33    var finalData = [];
34
35    for (var i = 0; i < startData.length; i++) {
36        if (startData[i].type === "text") {
37            var text = startData[i].data;
38
39            var lookingForLeft = true;
40            var currIndex = 0;
41            var nextIndex;
42
43            nextIndex = text.indexOf(leftDelim);
44            if (nextIndex !== -1) {
45                currIndex = nextIndex;
46                finalData.push({
47                    type: "text",
48                    data: text.slice(0, currIndex)
49                });
50                lookingForLeft = false;
51            }
52
53            while (true) {
54                if (lookingForLeft) {
55                    nextIndex = text.indexOf(leftDelim, currIndex);
56                    if (nextIndex === -1) {
57                        break;
58                    }
59
60                    finalData.push({
61                        type: "text",
62                        data: text.slice(currIndex, nextIndex)
63                    });
64
65                    currIndex = nextIndex;
66                } else {
67                    nextIndex = findEndOfMath(
68                        rightDelim,
69                        text,
70                        currIndex + leftDelim.length);
71                    if (nextIndex === -1) {
72                        break;
73                    }
74
75                    finalData.push({
76                        type: "math",
77                        data: text.slice(
78                            currIndex + leftDelim.length,
79                            nextIndex),
80                        rawData: text.slice(
81                            currIndex,
82                            nextIndex + rightDelim.length),
83                        display: display
84                    });
85
86                    currIndex = nextIndex + rightDelim.length;
87                }
88
89                lookingForLeft = !lookingForLeft;
90            }
91
92            finalData.push({
93                type: "text",
94                data: text.slice(currIndex)
95            });
96        } else {
97            finalData.push(startData[i]);
98        }
99    }
100
101    return finalData;
102};
103
104var splitWithDelimiters = function(text, delimiters) {
105    var data = [{type: "text", data: text}];
106    for (var i = 0; i < delimiters.length; i++) {
107        var delimiter = delimiters[i];
108        data = splitAtDelimiters(
109            data, delimiter.left, delimiter.right,
110            delimiter.display || false);
111    }
112    return data;
113};
114
115var renderMathInText = function(text, delimiters) {
116    var data = splitWithDelimiters(text, delimiters);
117
118    var fragment = document.createDocumentFragment();
119
120    for (var i = 0; i < data.length; i++) {
121        if (data[i].type === "text") {
122            fragment.appendChild(document.createTextNode(data[i].data));
123        } else {
124            var span = document.createElement("span");
125            var math = data[i].data;
126            try {
127                katex.render(math, span, {
128                    displayMode: data[i].display
129                });
130            } catch (e) {
131                if (!(e instanceof katex.ParseError)) {
132                    throw e;
133                }
134                console.error(
135                    "KaTeX auto-render: Failed to parse `" + data[i].data +
136                    "` with ",
137                    e
138                );
139                fragment.appendChild(document.createTextNode(data[i].rawData));
140                continue;
141            }
142            fragment.appendChild(span);
143        }
144    }
145
146    return fragment;
147};
148
149var renderElem = function(elem, delimiters, ignoredTags) {
150    for (var i = 0; i < elem.childNodes.length; i++) {
151        var childNode = elem.childNodes[i];
152        if (childNode.nodeType === 3) {
153            // Text node
154            var frag = renderMathInText(childNode.textContent, delimiters);
155            i += frag.childNodes.length - 1;
156            elem.replaceChild(frag, childNode);
157        } else if (childNode.nodeType === 1) {
158            // Element node
159            var shouldRender = ignoredTags.indexOf(
160                childNode.nodeName.toLowerCase()) === -1;
161
162            if (shouldRender) {
163                renderElem(childNode, delimiters, ignoredTags);
164            }
165        }
166        // Otherwise, it's something else, and ignore it.
167    }
168};
169
170var defaultOptions = {
171    delimiters: [
172        {left: "$$", right: "$$", display: true},
173        {left: "\\[", right: "\\]", display: true},
174        {left: "\\(", right: "\\)", display: false}
175        // LaTeX uses this, but it ruins the display of normal `$` in text:
176        // {left: "$", right: "$", display: false}
177    ],
178
179    ignoredTags: [
180        "script", "noscript", "style", "textarea", "pre", "code"
181    ]
182};
183
184var extend = function(obj) {
185    // Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
186    var source, prop;
187    for (var i = 1, length = arguments.length; i < length; i++) {
188        source = arguments[i];
189        for (prop in source) {
190            if (Object.prototype.hasOwnProperty.call(source, prop)) {
191                obj[prop] = source[prop];
192            }
193        }
194    }
195    return obj;
196};
197
198var renderMathInElement = function(elem, options) {
199    if (!elem) {
200        throw new Error("No element provided to render");
201    }
202
203    options = extend({}, defaultOptions, options);
204
205    renderElem(elem, options.delimiters, options.ignoredTags);
206};
207
208renderMathInElement(document.body);
209
210</script>
211