tex2jax.js 13 KB

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