www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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");