mml2jax.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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/mml2jax.js
  6. *
  7. * Implements the MathML to Jax preprocessor that locates <math> nodes
  8. * within the text of a document and replaces them with SCRIPT tags
  9. * for processing by MathJax.
  10. *
  11. * ---------------------------------------------------------------------
  12. *
  13. * Copyright (c) 2010-2017 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.mml2jax = {
  28. version: "2.7.2",
  29. config: {
  30. preview: "mathml" // Use the <math> element as the
  31. // preview. Set to "none" for no preview,
  32. // set to "alttext" to use the alttext attribute
  33. // of the <math> element, set to "altimg" to use
  34. // an image described by the altimg* attributes
  35. // or set to an array specifying an HTML snippet
  36. // to use a fixed preview for all math
  37. },
  38. MMLnamespace: "http://www.w3.org/1998/Math/MathML",
  39. PreProcess: function (element) {
  40. if (!this.configured) {
  41. this.config = MathJax.Hub.CombineConfig("mml2jax",this.config);
  42. if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)}
  43. this.InitBrowser();
  44. this.configured = true;
  45. }
  46. if (typeof(element) === "string") {element = document.getElementById(element)}
  47. if (!element) {element = document.body}
  48. var mathArray = [];
  49. //
  50. // Handle all math tags with no namespaces
  51. //
  52. this.PushMathElements(mathArray,element,"math");
  53. //
  54. // Handle math with namespaces in XHTML
  55. //
  56. this.PushMathElements(mathArray,element,"math",this.MMLnamespace);
  57. //
  58. // Handle math with namespaces in HTML
  59. //
  60. var i, m;
  61. if (typeof(document.namespaces) !== "undefined") {
  62. //
  63. // IE namespaces are listed in document.namespaces
  64. //
  65. try {
  66. for (i = 0, m = document.namespaces.length; i < m; i++) {
  67. var ns = document.namespaces[i];
  68. if (ns.urn === this.MMLnamespace)
  69. {this.PushMathElements(mathArray,element,ns.name+":math")}
  70. }
  71. } catch (err) {}
  72. } else {
  73. //
  74. // Everybody else
  75. //
  76. var html = document.getElementsByTagName("html")[0];
  77. if (html) {
  78. for (i = 0, m = html.attributes.length; i < m; i++) {
  79. var attr = html.attributes[i];
  80. if (attr.nodeName.substr(0,6) === "xmlns:" && attr.nodeValue === this.MMLnamespace)
  81. {this.PushMathElements(mathArray,element,attr.nodeName.substr(6)+":math")}
  82. }
  83. }
  84. }
  85. this.ProcessMathArray(mathArray);
  86. },
  87. PushMathElements: function (array,element,name,namespace) {
  88. var math, preview = MathJax.Hub.config.preRemoveClass;
  89. if (namespace) {
  90. if (!element.getElementsByTagNameNS) return;
  91. math = element.getElementsByTagNameNS(namespace,name);
  92. } else {
  93. math = element.getElementsByTagName(name);
  94. }
  95. for (var i = 0, m = math.length; i < m; i++) {
  96. var parent = math[i].parentNode;
  97. if (parent && parent.className !== preview &&
  98. !parent.isMathJax && !math[i].prefix === !namespace) array.push(math[i]);
  99. }
  100. },
  101. ProcessMathArray: function (math) {
  102. var i, m = math.length;
  103. if (m) {
  104. if (this.MathTagBug) {
  105. for (i = 0; i < m; i++) {
  106. if (math[i].nodeName === "MATH") {this.ProcessMathFlattened(math[i])}
  107. else {this.ProcessMath(math[i])}
  108. }
  109. } else {
  110. for (i = 0; i < m; i++) {this.ProcessMath(math[i])}
  111. }
  112. }
  113. },
  114. ProcessMath: function (math) {
  115. var parent = math.parentNode;
  116. if (!parent || parent.className === MathJax.Hub.config.preRemoveClass) return;
  117. var script = document.createElement("script");
  118. script.type = "math/mml";
  119. parent.insertBefore(script,math);
  120. if (this.AttributeBug) {
  121. var html = this.OuterHTML(math);
  122. if (this.CleanupHTML) {
  123. html = html.replace(/<\?import .*?>/i,"").replace(/<\?xml:namespace .*?\/>/i,"");
  124. html = html.replace(/&nbsp;/g,"&#xA0;");
  125. }
  126. MathJax.HTML.setScript(script,html); parent.removeChild(math);
  127. } else {
  128. var span = MathJax.HTML.Element("span"); span.appendChild(math);
  129. MathJax.HTML.setScript(script,span.innerHTML);
  130. }
  131. if (this.config.preview !== "none") {this.createPreview(math,script)}
  132. },
  133. ProcessMathFlattened: function (math) {
  134. var parent = math.parentNode;
  135. if (!parent || parent.className === MathJax.Hub.config.preRemoveClass) return;
  136. var script = document.createElement("script");
  137. script.type = "math/mml";
  138. parent.insertBefore(script,math);
  139. var mml = "", node, MATH = math;
  140. while (math && math.nodeName !== "/MATH") {
  141. node = math; math = math.nextSibling;
  142. mml += this.NodeHTML(node);
  143. node.parentNode.removeChild(node);
  144. }
  145. if (math && math.nodeName === "/MATH") {math.parentNode.removeChild(math)}
  146. script.text = mml + "</math>";
  147. if (this.config.preview !== "none") {this.createPreview(MATH,script)}
  148. },
  149. NodeHTML: function (node) {
  150. var html, i, m;
  151. if (node.nodeName === "#text") {
  152. html = this.quoteHTML(node.nodeValue);
  153. } else if (node.nodeName === "#comment") {
  154. html = "<!--" + node.nodeValue + "-->"
  155. } else {
  156. // In IE, outerHTML doesn't properly quote attributes, so quote them by hand
  157. // In Opera, HTML special characters aren't quoted in attributes, so quote them
  158. html = "<"+node.nodeName.toLowerCase();
  159. for (i = 0, m = node.attributes.length; i < m; i++) {
  160. var attribute = node.attributes[i];
  161. if (attribute.specified && attribute.nodeName.substr(0,10) !== "_moz-math-") {
  162. // Opera 11.5 beta turns xmlns into xmlns:xmlns, so put it back (*** check after 11.5 is out ***)
  163. html += " "+attribute.nodeName.toLowerCase().replace(/xmlns:xmlns/,"xmlns")+"=";
  164. var value = attribute.nodeValue; // IE < 8 doesn't properly set style by setAttributes
  165. if (value == null && attribute.nodeName === "style" && node.style) {value = node.style.cssText}
  166. html += '"'+this.quoteHTML(value)+'"';
  167. }
  168. }
  169. html += ">";
  170. // Handle internal HTML (possibly due to <semantics> annotation or missing </math>)
  171. if (node.outerHTML != null && node.outerHTML.match(/(.<\/[A-Z]+>|\/>)$/)) {
  172. for (i = 0, m = node.childNodes.length; i < m; i++)
  173. {html += this.OuterHTML(node.childNodes[i])}
  174. html += "</"+node.nodeName.toLowerCase()+">";
  175. }
  176. }
  177. return html;
  178. },
  179. OuterHTML: function (node) {
  180. if (node.nodeName.charAt(0) === "#") {return this.NodeHTML(node)}
  181. if (!this.AttributeBug) {return node.outerHTML}
  182. var html = this.NodeHTML(node);
  183. for (var i = 0, m = node.childNodes.length; i < m; i++)
  184. {html += this.OuterHTML(node.childNodes[i]);}
  185. html += "</"+node.nodeName.toLowerCase()+">";
  186. return html;
  187. },
  188. quoteHTML: function (string) {
  189. if (string == null) {string = ""}
  190. return string.replace(/&/g,"&#x26;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\"/g,"&quot;");
  191. },
  192. createPreview: function (math,script) {
  193. var preview = this.config.preview;
  194. if (preview === "none") return;
  195. var isNodePreview = false;
  196. var previewClass = MathJax.Hub.config.preRemoveClass;
  197. if ((script.previousSibling||{}).className === previewClass) return;
  198. if (preview === "mathml") {
  199. isNodePreview = true;
  200. // mathml preview does not work with IE < 9, so fallback to alttext.
  201. if (this.MathTagBug) {preview = "alttext"} else {preview = math.cloneNode(true)}
  202. }
  203. if (preview === "alttext" || preview === "altimg") {
  204. isNodePreview = true;
  205. var alttext = this.filterPreview(math.getAttribute("alttext"));
  206. if (preview === "alttext") {
  207. if (alttext != null) {preview = MathJax.HTML.TextNode(alttext)} else {preview = null}
  208. } else {
  209. var src = math.getAttribute("altimg");
  210. if (src != null) {
  211. // FIXME: use altimg-valign when display="inline"?
  212. var style = {width: math.getAttribute("altimg-width"), height: math.getAttribute("altimg-height")};
  213. preview = MathJax.HTML.Element("img",{src:src,alt:alttext,style:style});
  214. } else {preview = null}
  215. }
  216. }
  217. if (preview) {
  218. var span;
  219. if (isNodePreview) {
  220. span = MathJax.HTML.Element("span",{className:previewClass});
  221. span.appendChild(preview);
  222. } else {
  223. span = MathJax.HTML.Element("span",{className:previewClass},preview);
  224. }
  225. script.parentNode.insertBefore(span,script);
  226. }
  227. },
  228. filterPreview: function (text) {return text},
  229. InitBrowser: function () {
  230. var test = MathJax.HTML.Element("span",{id:"<", className: "mathjax", innerHTML: "<math><mi>x</mi><mspace /></math>"});
  231. var html = test.outerHTML || "";
  232. this.AttributeBug = html !== "" && !(
  233. html.match(/id="&lt;"/) && // "<" should convert to "&lt;"
  234. html.match(/class="mathjax"/) && // IE leaves out quotes
  235. html.match(/<\/math>/) // Opera 9 drops tags after self-closing tags
  236. );
  237. this.MathTagBug = test.childNodes.length > 1; // IE < 9 flattens unknown tags
  238. this.CleanupHTML = MathJax.Hub.Browser.isMSIE; // remove namespace and other added tags
  239. }
  240. };
  241. //
  242. // We register the preprocessors with the following priorities:
  243. // - mml2jax.js: 5
  244. // - jsMath2jax.js: 8
  245. // - asciimath2jax.js, tex2jax.js: 10 (default)
  246. // See issues 18 and 484 and the other *2jax.js files.
  247. //
  248. MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.mml2jax],5);
  249. MathJax.Ajax.loadComplete("[MathJax]/extensions/mml2jax.js");