tex2jax.js (13281B)
1 /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 4 /************************************************************* 5 * 6 * MathJax/extensions/tex2jax.js 7 * 8 * Implements the TeX to Jax preprocessor that locates TeX code 9 * within the text of a document and replaces it with SCRIPT tags 10 * for processing by MathJax. 11 * 12 * --------------------------------------------------------------------- 13 * 14 * Copyright (c) 2009-2015 The MathJax Consortium 15 * 16 * Licensed under the Apache License, Version 2.0 (the "License"); 17 * you may not use this file except in compliance with the License. 18 * You may obtain a copy of the License at 19 * 20 * http://www.apache.org/licenses/LICENSE-2.0 21 * 22 * Unless required by applicable law or agreed to in writing, software 23 * distributed under the License is distributed on an "AS IS" BASIS, 24 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 * See the License for the specific language governing permissions and 26 * limitations under the License. 27 */ 28 29 MathJax.Extension.tex2jax = { 30 version: "2.6.0", 31 config: { 32 inlineMath: [ // The start/stop pairs for in-line math 33 // ['$','$'], // (comment out any you don't want, or add your own, but 34 ['\\(','\\)'] // be sure that you don't have an extra comma at the end) 35 ], 36 37 displayMath: [ // The start/stop pairs for display math 38 ['$$','$$'], // (comment out any you don't want, or add your own, but 39 ['\\[','\\]'] // be sure that you don't have an extra comma at the end) 40 ], 41 42 balanceBraces: true, // determines whether tex2jax requires braces to be 43 // balanced within math delimiters (allows for nested 44 // dollar signs). Set to false to get pre-v2.0 compatibility. 45 46 skipTags: ["script","noscript","style","textarea","pre","code","annotation","annotation-xml"], 47 // The names of the tags whose contents will not be 48 // scanned for math delimiters 49 50 ignoreClass: "tex2jax_ignore", // the class name of elements whose contents should 51 // NOT be processed by tex2jax. Note that this 52 // is a regular expression, so be sure to quote any 53 // regexp special characters 54 55 processClass: "tex2jax_process", // the class name of elements whose contents SHOULD 56 // be processed when they appear inside ones that 57 // are ignored. Note that this is a regular expression, 58 // so be sure to quote any regexp special characters 59 60 processEscapes: false, // set to true to allow \$ to produce a dollar without 61 // starting in-line math mode 62 63 processEnvironments: true, // set to true to process \begin{xxx}...\end{xxx} outside 64 // of math mode, false to prevent that 65 66 processRefs: true, // set to true to process \ref{...} outside of math mode 67 68 69 preview: "TeX" // set to "none" to not insert MathJax_Preview spans 70 // or set to an array specifying an HTML snippet 71 // to use the same preview for every equation. 72 73 }, 74 75 PreProcess: function (element) { 76 if (!this.configured) { 77 this.config = MathJax.Hub.CombineConfig("tex2jax",this.config); 78 if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)} 79 if (typeof(this.config.previewTeX) !== "undefined" && !this.config.previewTeX) 80 {this.config.preview = "none"} // backward compatibility for previewTeX parameter 81 this.configured = true; 82 } 83 if (typeof(element) === "string") {element = document.getElementById(element)} 84 if (!element) {element = document.body} 85 if (this.createPatterns()) {this.scanElement(element,element.nextSibling)} 86 }, 87 88 createPatterns: function () { 89 var starts = [], parts = [], i, m, config = this.config; 90 this.match = {}; 91 for (i = 0, m = config.inlineMath.length; i < m; i++) { 92 starts.push(this.patternQuote(config.inlineMath[i][0])); 93 this.match[config.inlineMath[i][0]] = { 94 mode: "", 95 end: config.inlineMath[i][1], 96 pattern: this.endPattern(config.inlineMath[i][1]) 97 }; 98 } 99 for (i = 0, m = config.displayMath.length; i < m; i++) { 100 starts.push(this.patternQuote(config.displayMath[i][0])); 101 this.match[config.displayMath[i][0]] = { 102 mode: "; mode=display", 103 end: config.displayMath[i][1], 104 pattern: this.endPattern(config.displayMath[i][1]) 105 }; 106 } 107 if (starts.length) {parts.push(starts.sort(this.sortLength).join("|"))} 108 if (config.processEnvironments) {parts.push("\\\\begin\\{([^}]*)\\}")} 109 if (config.processEscapes) {parts.push("\\\\*\\\\\\\$")} 110 if (config.processRefs) {parts.push("\\\\(eq)?ref\\{[^}]*\\}")} 111 this.start = new RegExp(parts.join("|"),"g"); 112 this.skipTags = new RegExp("^("+config.skipTags.join("|")+")$","i"); 113 var ignore = []; 114 if (MathJax.Hub.config.preRemoveClass) {ignore.push(MathJax.Hub.config.preRemoveClass)}; 115 if (config.ignoreClass) {ignore.push(config.ignoreClass)} 116 this.ignoreClass = (ignore.length ? new RegExp("(^| )("+ignore.join("|")+")( |$)") : /^$/); 117 this.processClass = new RegExp("(^| )("+config.processClass+")( |$)"); 118 return (parts.length > 0); 119 }, 120 121 patternQuote: function (s) {return s.replace(/([\^$(){}+*?\-|\[\]\:\\])/g,'\\$1')}, 122 123 endPattern: function (end) { 124 return new RegExp(this.patternQuote(end)+"|\\\\.|[{}]","g"); 125 }, 126 127 sortLength: function (a,b) { 128 if (a.length !== b.length) {return b.length - a.length} 129 return (a == b ? 0 : (a < b ? -1 : 1)); 130 }, 131 132 scanElement: function (element,stop,ignore) { 133 var cname, tname, ignoreChild, process; 134 while (element && element != stop) { 135 if (element.nodeName.toLowerCase() === '#text') { 136 if (!ignore) {element = this.scanText(element)} 137 } else { 138 cname = (typeof(element.className) === "undefined" ? "" : element.className); 139 tname = (typeof(element.tagName) === "undefined" ? "" : element.tagName); 140 if (typeof(cname) !== "string") {cname = String(cname)} // jsxgraph uses non-string class names! 141 process = this.processClass.exec(cname); 142 if (element.firstChild && !cname.match(/(^| )MathJax/) && 143 (process || !this.skipTags.exec(tname))) { 144 ignoreChild = (ignore || this.ignoreClass.exec(cname)) && !process; 145 this.scanElement(element.firstChild,stop,ignoreChild); 146 } 147 } 148 if (element) {element = element.nextSibling} 149 } 150 }, 151 152 scanText: function (element) { 153 if (element.nodeValue.replace(/\s+/,'') == '') {return element} 154 var match, prev; 155 this.search = {start: true}; 156 this.pattern = this.start; 157 while (element) { 158 this.pattern.lastIndex = 0; 159 while (element && element.nodeName.toLowerCase() === '#text' && 160 (match = this.pattern.exec(element.nodeValue))) { 161 if (this.search.start) {element = this.startMatch(match,element)} 162 else {element = this.endMatch(match,element)} 163 } 164 if (this.search.matched) {element = this.encloseMath(element)} 165 if (element) { 166 do {prev = element; element = element.nextSibling} 167 while (element && (element.nodeName.toLowerCase() === 'br' || 168 element.nodeName.toLowerCase() === '#comment')); 169 if (!element || element.nodeName !== '#text') 170 {return (this.search.close ? this.prevEndMatch() : prev)} 171 } 172 } 173 return element; 174 }, 175 176 startMatch: function (match,element) { 177 var delim = this.match[match[0]]; 178 if (delim != null) { // a start delimiter 179 this.search = { 180 end: delim.end, mode: delim.mode, pcount: 0, 181 open: element, olen: match[0].length, opos: this.pattern.lastIndex - match[0].length 182 }; 183 this.switchPattern(delim.pattern); 184 } else if (match[0].substr(0,6) === "\\begin") { // \begin{...} 185 this.search = { 186 end: "\\end{"+match[1]+"}", mode: "; mode=display", pcount: 0, 187 open: element, olen: 0, opos: this.pattern.lastIndex - match[0].length, 188 isBeginEnd: true 189 }; 190 this.switchPattern(this.endPattern(this.search.end)); 191 } else if (match[0].substr(0,4) === "\\ref" || match[0].substr(0,6) === "\\eqref") { 192 this.search = { 193 mode: "", end: "", open: element, pcount: 0, 194 olen: 0, opos: this.pattern.lastIndex - match[0].length 195 } 196 return this.endMatch([""],element); 197 } else { // escaped dollar signs 198 // put $ in a span so it doesn't get processed again 199 // split off backslashes so they don't get removed later 200 var slashes = match[0].substr(0,match[0].length-1), n, span; 201 if (slashes.length % 2 === 0) {span = [slashes.replace(/\\\\/g,"\\")]; n = 1} 202 else {span = [slashes.substr(1).replace(/\\\\/g,"\\"),"$"]; n = 0} 203 span = MathJax.HTML.Element("span",null,span); 204 var text = MathJax.HTML.TextNode(element.nodeValue.substr(0,match.index)); 205 element.nodeValue = element.nodeValue.substr(match.index + match[0].length - n); 206 element.parentNode.insertBefore(span,element); 207 element.parentNode.insertBefore(text,span); 208 this.pattern.lastIndex = n; 209 } 210 return element; 211 }, 212 213 endMatch: function (match,element) { 214 var search = this.search; 215 if (match[0] == search.end) { 216 if (!search.close || search.pcount === 0) { 217 search.close = element; 218 search.cpos = this.pattern.lastIndex; 219 search.clen = (search.isBeginEnd ? 0 : match[0].length); 220 } 221 if (search.pcount === 0) { 222 search.matched = true; 223 element = this.encloseMath(element); 224 this.switchPattern(this.start); 225 } 226 } 227 else if (match[0] === "{") {search.pcount++} 228 else if (match[0] === "}" && search.pcount) {search.pcount--} 229 return element; 230 }, 231 prevEndMatch: function () { 232 this.search.matched = true; 233 var element = this.encloseMath(this.search.close); 234 this.switchPattern(this.start); 235 return element; 236 }, 237 238 switchPattern: function (pattern) { 239 pattern.lastIndex = this.pattern.lastIndex; 240 this.pattern = pattern; 241 this.search.start = (pattern === this.start); 242 }, 243 244 encloseMath: function (element) { 245 var search = this.search, close = search.close, CLOSE, math; 246 if (search.cpos === close.length) {close = close.nextSibling} 247 else {close = close.splitText(search.cpos)} 248 if (!close) {CLOSE = close = MathJax.HTML.addText(search.close.parentNode,"")} 249 search.close = close; 250 math = (search.opos ? search.open.splitText(search.opos) : search.open); 251 while (math.nextSibling && math.nextSibling !== close) { 252 if (math.nextSibling.nodeValue !== null) { 253 if (math.nextSibling.nodeName === "#comment") { 254 math.nodeValue += math.nextSibling.nodeValue.replace(/^\[CDATA\[((.|\n|\r)*)\]\]$/,"$1"); 255 } else { 256 math.nodeValue += math.nextSibling.nodeValue; 257 } 258 } else if (this.msieNewlineBug) { 259 math.nodeValue += (math.nextSibling.nodeName.toLowerCase() === "br" ? "\n" : " "); 260 } else { 261 math.nodeValue += " "; 262 } 263 math.parentNode.removeChild(math.nextSibling); 264 } 265 var TeX = math.nodeValue.substr(search.olen,math.nodeValue.length-search.olen-search.clen); 266 math.parentNode.removeChild(math); 267 if (this.config.preview !== "none") {this.createPreview(search.mode,TeX)} 268 math = this.createMathTag(search.mode,TeX); 269 this.search = {}; this.pattern.lastIndex = 0; 270 if (CLOSE) {CLOSE.parentNode.removeChild(CLOSE)} 271 return math; 272 }, 273 274 insertNode: function (node) { 275 var search = this.search; 276 search.close.parentNode.insertBefore(node,search.close); 277 }, 278 279 createPreview: function (mode,tex) { 280 var preview = this.config.preview; 281 if (preview === "none") return; 282 if (preview === "TeX") {preview = [this.filterPreview(tex)]} 283 if (preview) { 284 preview = MathJax.HTML.Element("span",{className:MathJax.Hub.config.preRemoveClass},preview); 285 this.insertNode(preview); 286 } 287 }, 288 289 createMathTag: function (mode,tex) { 290 var script = document.createElement("script"); 291 script.type = "math/tex" + mode; 292 MathJax.HTML.setScript(script,tex); 293 this.insertNode(script); 294 return script; 295 }, 296 297 filterPreview: function (tex) {return tex}, 298 299 msieNewlineBug: (MathJax.Hub.Browser.isMSIE && document.documentMode < 9) 300 301 }; 302 303 // We register the preprocessors with the following priorities: 304 // - mml2jax.js: 5 305 // - jsMath2jax.js: 8 306 // - asciimath2jax.js, tex2jax.js: 10 (default) 307 // See issues 18 and 484 and the other *2jax.js files. 308 MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.tex2jax]); 309 MathJax.Ajax.loadComplete("[MathJax]/extensions/tex2jax.js");