asciimath2jax.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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/asciimath2jax.js
  6. *
  7. * Implements the AsciiMath to Jax preprocessor that locates AsciiMath
  8. * code within the text of a document and replaces it with SCRIPT tags for
  9. * processing by MathJax.
  10. *
  11. * Modified by David Lippman, based on tex2jax.js.
  12. * Additional work by Davide P. Cervone.
  13. *
  14. * ---------------------------------------------------------------------
  15. *
  16. * Copyright (c) 2012-2013 The MathJax Consortium
  17. *
  18. * Licensed under the Apache License, Version 2.0 (the "License");
  19. * you may not use this file except in compliance with the License.
  20. * You may obtain a copy of the License at
  21. *
  22. * http://www.apache.org/licenses/LICENSE-2.0
  23. *
  24. * Unless required by applicable law or agreed to in writing, software
  25. * distributed under the License is distributed on an "AS IS" BASIS,
  26. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27. * See the License for the specific language governing permissions and
  28. * limitations under the License.
  29. */
  30. MathJax.Extension.asciimath2jax = {
  31. version: "2.2",
  32. config: {
  33. delimiters: [['`','`']], // The star/stop delimiter pairs for asciimath code
  34. skipTags: ["script","noscript","style","textarea","pre","code"],
  35. // The names of the tags whose contents will not be
  36. // scanned for math delimiters
  37. ignoreClass: "asciimath2jax_ignore", // the class name of elements whose contents should
  38. // NOT be processed by asciimath2jax. Note that this
  39. // is a regular expression, so be sure to quote any
  40. // regexp special characters
  41. processClass: "asciimath2jax_process", // the class name of elements whose contents SHOULD
  42. // be processed when they appear inside ones that
  43. // are ignored. Note that this is a regular expression,
  44. // so be sure to quote any regexp special characters
  45. preview: "AsciiMath" // set to "none" to not insert MathJax_Preview spans
  46. // or set to an array specifying an HTML snippet
  47. // to use the same preview for every equation.
  48. },
  49. PreProcess: function (element) {
  50. if (!this.configured) {
  51. this.config = MathJax.Hub.CombineConfig("asciimath2jax",this.config);
  52. if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)}
  53. this.configured = true;
  54. }
  55. if (typeof(element) === "string") {element = document.getElementById(element)}
  56. if (!element) {element = document.body}
  57. if (this.createPatterns()) {this.scanElement(element,element.nextSibling)}
  58. },
  59. createPatterns: function () {
  60. var starts = [], i, m, config = this.config; this.match = {};
  61. if (config.delimiters.length === 0) {return false}
  62. for (i = 0, m = config.delimiters.length; i < m; i++) {
  63. starts.push(this.patternQuote(config.delimiters[i][0]));
  64. this.match[config.delimiters[i][0]] = {
  65. mode: "",
  66. end: config.delimiters[i][1],
  67. pattern: this.endPattern(config.delimiters[i][1])
  68. };
  69. }
  70. this.start = new RegExp(starts.sort(this.sortLength).join("|"),"g");
  71. this.skipTags = new RegExp("^("+config.skipTags.join("|")+")$","i");
  72. var ignore = [];
  73. if (MathJax.Hub.config.preRemoveClass) {ignore.push(MathJax.Hub.config.preRemoveClass)}
  74. if (config.ignoreClass) {ignore.push(config.ignoreClass)}
  75. this.ignoreClass = (ignore.length ? new RegExp("(^| )("+ignore.join("|")+")( |$)") : /^$/);
  76. this.processClass = new RegExp("(^| )("+config.processClass+")( |$)");
  77. return true;
  78. },
  79. patternQuote: function (s) {return s.replace(/([\^$(){}+*?\-|\[\]\:\\])/g,'\\$1')},
  80. endPattern: function (end) {
  81. return new RegExp(this.patternQuote(end)+"|\\\\.","g");
  82. },
  83. sortLength: function (a,b) {
  84. if (a.length !== b.length) {return b.length - a.length}
  85. return (a == b ? 0 : (a < b ? -1 : 1));
  86. },
  87. scanElement: function (element,stop,ignore) {
  88. var cname, tname, ignoreChild, process;
  89. while (element && element != stop) {
  90. if (element.nodeName.toLowerCase() === '#text') {
  91. if (!ignore) {element = this.scanText(element)}
  92. } else {
  93. cname = (typeof(element.className) === "undefined" ? "" : element.className);
  94. tname = (typeof(element.tagName) === "undefined" ? "" : element.tagName);
  95. if (typeof(cname) !== "string") {cname = String(cname)} // jsxgraph uses non-string class names!
  96. process = this.processClass.exec(cname);
  97. if (element.firstChild && !cname.match(/(^| )MathJax/) &&
  98. (process || !this.skipTags.exec(tname))) {
  99. ignoreChild = (ignore || this.ignoreClass.exec(cname)) && !process;
  100. this.scanElement(element.firstChild,stop,ignoreChild);
  101. }
  102. }
  103. if (element) {element = element.nextSibling}
  104. }
  105. },
  106. scanText: function (element) {
  107. if (element.nodeValue.replace(/\s+/,'') == '') {return element}
  108. var match, prev;
  109. this.search = {start: true};
  110. this.pattern = this.start;
  111. while (element) {
  112. this.pattern.lastIndex = 0;
  113. while (element && element.nodeName.toLowerCase() === '#text' &&
  114. (match = this.pattern.exec(element.nodeValue))) {
  115. if (this.search.start) {element = this.startMatch(match,element)}
  116. else {element = this.endMatch(match,element)}
  117. }
  118. if (this.search.matched) {element = this.encloseMath(element)}
  119. if (element) {
  120. do {prev = element; element = element.nextSibling}
  121. while (element && (element.nodeName.toLowerCase() === 'br' ||
  122. element.nodeName.toLowerCase() === '#comment'));
  123. if (!element || element.nodeName !== '#text') {return prev}
  124. }
  125. }
  126. return element;
  127. },
  128. startMatch: function (match,element) {
  129. var delim = this.match[match[0]];
  130. if (delim != null) {
  131. this.search = {
  132. end: delim.end, mode: delim.mode,
  133. open: element, olen: match[0].length,
  134. opos: this.pattern.lastIndex - match[0].length
  135. };
  136. this.switchPattern(delim.pattern);
  137. }
  138. return element;
  139. },
  140. endMatch: function (match,element) {
  141. if (match[0] == this.search.end) {
  142. this.search.close = element;
  143. this.search.cpos = this.pattern.lastIndex;
  144. this.search.clen = (this.search.isBeginEnd ? 0 : match[0].length);
  145. this.search.matched = true;
  146. element = this.encloseMath(element);
  147. this.switchPattern(this.start);
  148. }
  149. return element;
  150. },
  151. switchPattern: function (pattern) {
  152. pattern.lastIndex = this.pattern.lastIndex;
  153. this.pattern = pattern;
  154. this.search.start = (pattern === this.start);
  155. },
  156. encloseMath: function (element) {
  157. var search = this.search, close = search.close, CLOSE, math;
  158. if (search.cpos === close.length) {close = close.nextSibling}
  159. else {close = close.splitText(search.cpos)}
  160. if (!close) {CLOSE = close = MathJax.HTML.addText(search.close.parentNode,"")}
  161. search.close = close;
  162. math = (search.opos ? search.open.splitText(search.opos) : search.open);
  163. while (math.nextSibling && math.nextSibling !== close) {
  164. if (math.nextSibling.nodeValue !== null) {
  165. if (math.nextSibling.nodeName === "#comment") {
  166. math.nodeValue += math.nextSibling.nodeValue.replace(/^\[CDATA\[((.|\n|\r)*)\]\]$/,"$1");
  167. } else {
  168. math.nodeValue += math.nextSibling.nodeValue;
  169. }
  170. } else if (this.msieNewlineBug) {
  171. math.nodeValue += (math.nextSibling.nodeName.toLowerCase() === "br" ? "\n" : " ");
  172. } else {
  173. math.nodeValue += " ";
  174. }
  175. math.parentNode.removeChild(math.nextSibling);
  176. }
  177. var AM = math.nodeValue.substr(search.olen,math.nodeValue.length-search.olen-search.clen);
  178. math.parentNode.removeChild(math);
  179. if (this.config.preview !== "none") {this.createPreview(search.mode,AM)}
  180. math = this.createMathTag(search.mode,AM);
  181. this.search = {}; this.pattern.lastIndex = 0;
  182. if (CLOSE) {CLOSE.parentNode.removeChild(CLOSE)}
  183. return math;
  184. },
  185. insertNode: function (node) {
  186. var search = this.search;
  187. search.close.parentNode.insertBefore(node,search.close);
  188. },
  189. createPreview: function (mode,asciimath) {
  190. var preview = this.config.preview;
  191. if (preview === "none") return;
  192. if (preview === "AsciiMath") {preview = [this.filterPreview(asciimath)]}
  193. if (preview) {
  194. preview = MathJax.HTML.Element("span",{className:MathJax.Hub.config.preRemoveClass},preview);
  195. this.insertNode(preview);
  196. }
  197. },
  198. createMathTag: function (mode,asciimath) {
  199. var script = document.createElement("script");
  200. script.type = "math/asciimath" + mode;
  201. MathJax.HTML.setScript(script,asciimath);
  202. this.insertNode(script);
  203. return script;
  204. },
  205. filterPreview: function (asciimath) {return asciimath},
  206. msieNewlineBug: (MathJax.Hub.Browser.isMSIE && (document.documentMode||0) < 9)
  207. };
  208. MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.asciimath2jax]);
  209. MathJax.Ajax.loadComplete("[MathJax]/extensions/asciimath2jax.js");