Annotation of loncom/html/adm/jsMath/plugins/tex2math.js, revision 1.2

1.1       albertel    1: /*
                      2:  *  tex2math.js
                      3:  *  
                      4:  *  Part of the jsMath package for mathematics on the web.
                      5:  *
                      6:  *  This file is a plugin that searches text wthin a web page
                      7:  *  for \(...\), \[...\], $...$ and $$...$$ and converts them to
                      8:  *  the appropriate <SPAN CLASS="math">...</SPAN> or
                      9:  *  <DIV CLASS="math">...</DIV> tags.
                     10:  *
                     11:  *  ---------------------------------------------------------------------
                     12:  *
1.2     ! albertel   13:  *  Copyright 2004-2006 by Davide P. Cervone
        !            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.
1.1       albertel   26:  */
                     27: 
1.2     ! albertel   28: if (!jsMath.tex2math) {jsMath.tex2math = {}}  // make sure jsMath.tex2math is defined
        !            29: if (!jsMath.tex2math.loaded) {                // only load it once
        !            30: 
        !            31: if (!jsMath.Controls) {jsMath.Controls = {}}
        !            32: if (!jsMath.Controls.cookie) {jsMath.Controls.cookie = {}}
        !            33: 
        !            34: jsMath.Add(jsMath.tex2math,{
1.1       albertel   35:   
1.2     ! albertel   36:   loaded: 1,
        !            37:   window: window,
        !            38: 
1.1       albertel   39:   /*
1.2     ! albertel   40:    *  Call the main conversion routine with appropriate flags
1.1       albertel   41:    */
1.2     ! albertel   42: 
1.1       albertel   43:   ConvertTeX: function (element) {
1.2     ! albertel   44:     this.Convert(element,{
1.1       albertel   45:       processSingleDollars: 1, processDoubleDollars: 1,
                     46:       processSlashParens: 1, processSlashBrackets: 1,
                     47:       custom: 0, fixEscapedDollars: 1
                     48:     });
                     49:   },
                     50:   
                     51:   ConvertTeX2: function (element) {
1.2     ! albertel   52:     this.Convert(element,{
1.1       albertel   53:       processSingleDollars: 0, processDoubleDollars: 1,
                     54:       processSlashParens: 1, processSlashBrackets: 1,
                     55:       custom: 0, fixEscapedDollars: 0
                     56:     });
                     57:   },
                     58:   
                     59:   ConvertLaTeX: function (element) {
1.2     ! albertel   60:     this.Convert(element,{
1.1       albertel   61:       processSingleDollars: 0, processDoubleDollars: 0,
                     62:       processSlashParens: 1, processSlashBrackets: 1,
                     63:       custom: 0, fixEscapedDollars: 0
                     64:     });
                     65:   },
                     66:   
                     67:   ConvertCustom: function (element) {
1.2     ! albertel   68:     this.Convert(element,{custom: 1, fixEscapedDollars: 0});
1.1       albertel   69:   },
1.2     ! albertel   70:   
        !            71:   /*******************************************************************/
1.1       albertel   72: 
                     73:   /*
1.2     ! albertel   74:    *  Define a custom search by indicating the
        !            75:    *  strings to use for starting and ending
        !            76:    *  in-line and display mathematics
1.1       albertel   77:    */
1.2     ! albertel   78:   CustomSearch: function (iOpen,iClose,dOpen,dClose) {
        !            79:     this.inLineOpen = iOpen; this.inLineClose = iClose;
        !            80:     this.displayOpen = dOpen; this.displayClose = dClose;
        !            81:     this.createPattern('customPattern',new RegExp(
        !            82:       '('+this.patternQuote(dOpen)+'|'
        !            83:          +this.patternQuote(iOpen)+'|'
        !            84:          +this.patternQuote(dClose)+'|'
        !            85:          +this.patternQuote(iClose)+'|\\\\.)','g'
        !            86:     ));
        !            87:   },
        !            88:   
        !            89:   patternQuote: function (s) {
        !            90:     s = s.replace(/([\^(){}+*?\-|\[\]\:\\])/g,'\\$1');
        !            91:     return s;
        !            92:   },
        !            93: 
        !            94:   /*
        !            95:    *  MSIE on the Mac doesn't handle lastIndex correctly, so
        !            96:    *  override it and implement it correctly.
        !            97:    */
        !            98:   createPattern: function (name,pattern) {
        !            99:     jsMath.tex2math[name] = pattern;
        !           100:     if (this.fixPatterns) {
        !           101:       pattern.oldExec = pattern.exec;
        !           102:       pattern.exec = this.msiePatternExec;
        !           103:     }
        !           104:   },
        !           105:   msiePatternExec: function (string) {
        !           106:     if (this.lastIndex == null) (this.lastIndex = 0);
        !           107:     var match = this.oldExec(string.substr(this.lastIndex));
        !           108:     if (match) {this.lastIndex += match.lastIndex}
        !           109:           else {this.lastIndex = null}
        !           110:     return match;
        !           111:   },
1.1       albertel  112: 
1.2     ! albertel  113:   /*******************************************************************/
1.1       albertel  114: 
1.2     ! albertel  115:   /*
        !           116:    *  Set up for the correct type of search, and recursively
        !           117:    *  convert the mathematics.  Disable tex2math if the cookie
        !           118:    *  isn't set, or of there is an element with ID of 'tex2math_off'.
        !           119:    */
        !           120:   Convert: function (element,flags) {
        !           121:     this.Init();
        !           122:     if (!element) {element = jsMath.document.body}
        !           123:     if (typeof(element) == 'string') {element = jsMath.document.getElementById(element)}
        !           124:     if (jsMath.Controls.cookie.tex2math && 
        !           125:         (!jsMath.tex2math.allowDisableTag || !jsMath.document.getElementById('tex2math_off'))) {
        !           126:       this.custom = 0; for (var i in flags) {this[i] = flags[i]}
        !           127:       if (this.custom) {
        !           128:         this.pattern = this.customPattern;
        !           129:         this.ProcessMatch = this.customProcessMatch;
        !           130:       } else {
        !           131:         this.pattern = this.stdPattern;
        !           132:         this.ProcessMatch = this.stdProcessMatch;
1.1       albertel  133:       }
1.2     ! albertel  134:       if (this.processDoubleDollars || this.processSingleDollars ||
        !           135:           this.processSlashParens   || this.processSlashBrackets ||
        !           136:           this.custom) this.ScanElement(element);
        !           137:     }
        !           138:   },
1.1       albertel  139: 
1.2     ! albertel  140:   /*
        !           141:    *  Recursively look through a document for text nodes that could
        !           142:    *  contain mathematics.
        !           143:    */
        !           144:   ScanElement: function (element,ignore) {
        !           145:     if (!element) {element = jsMath.document.body}
        !           146:     if (typeof(element) == 'string') {element = jsMath.document.getElementById(element)}
        !           147:     while (element) {
        !           148:       if (element.nodeName == '#text') {
        !           149:         if (!ignore) {element = this.ScanText(element)}
        !           150:       } else if (element.firstChild && element.className != 'math') {
        !           151:         var off = ignore || element.className == 'tex2math_ignore' ||
        !           152:            (element.tagName && element.tagName.match(/^(script|noscript|style|textarea|pre)$/i));
        !           153:         off = off && element.className != 'tex2math_process';
        !           154:         this.ScanElement(element.firstChild,off);
1.1       albertel  155:       }
1.2     ! albertel  156:       if (element) {element = element.nextSibling}
        !           157:     }
        !           158:   },
        !           159:   
        !           160:   /*
        !           161:    *  Looks through a text element for math delimiters and
        !           162:    *  process them.  If <BR> tags are found in the middle, they
        !           163:    *  are ignored (this is for BBS systems that have editors
        !           164:    *  that insert these automatically).
        !           165:    */
        !           166:   ScanText: function (element) {
        !           167:     if (element.nodeValue.replace(/\s+/,'') == '') {return element}
        !           168:     var match; var prev; this.search = {};
        !           169:     while (element) {
        !           170:       this.pattern.lastIndex = 0;
        !           171:       while (element && element.nodeName == '#text' &&
        !           172:             (match = this.pattern.exec(element.nodeValue))) {
        !           173:         element = this.ProcessMatch(match[0],match.index,element);
1.1       albertel  174:       }
1.2     ! albertel  175:       if (this.search.matched) {element = this.EncloseMath(element)}
        !           176:       if (!element) {return null}
        !           177:       prev = element; element = element.nextSibling;
        !           178:       while (element && element.nodeName.toLowerCase() == 'br')
        !           179:         {prev = element; element = element.nextSibling}
        !           180:       if (!element || element.nodeName != '#text') {return prev}
        !           181:     }
        !           182:     return element;
        !           183:   },
        !           184:   
        !           185:   /*
        !           186:    *  If a matching end tag has been found, process the mathematics.
        !           187:    *  Otherwise, update the search data for the given delimiter,
        !           188:    *  or ignore it, as the item dictates.
        !           189:    */
        !           190:   stdProcessMatch: function (match,index,element) {
        !           191:     if (match == this.search.end) {
        !           192:       this.search.close = element;
        !           193:       this.search.clength = match.length;
        !           194:       this.search.cpos = this.pattern.lastIndex;
        !           195:       element = this.EncloseMath(element);
        !           196:     } else {
        !           197:       switch (match) {
        !           198:         case '\\(':
        !           199:           if (this.search.end != '$' && this.search.end != '$$' &&
        !           200:               this.processSlashParens) {
        !           201:             this.ScanMark('span',element,'\\)');
        !           202:           }
        !           203:           break;
        !           204: 
        !           205:         case '\\[':
        !           206:           if (this.search.end != '$' && this.search.end != '$$' &&
        !           207:               this.processSlashBrackets) {
        !           208:             this.ScanMark('div',element,'\\]');
        !           209:           }
        !           210:           break;
        !           211: 
        !           212:         case '$$':
        !           213:           if (this.processDoubleDollars) {
        !           214:             var type = (this.doubleDollarsAreInLine? 'span': 'div');
        !           215:             this.ScanMark(type,element,'$$');
        !           216:           }
        !           217:           break;
        !           218: 
        !           219:         case '$':
        !           220:           if (this.search.end == null && this.processSingleDollars) {
        !           221:             this.ScanMark('span',element,'$');
        !           222:           }
        !           223:           break;
        !           224: 
        !           225:         case '\\$':
        !           226:           if (this.search.end == null && this.fixEscapedDollars) {
        !           227:             element.nodeValue = element.nodeValue.substr(0,index)
        !           228:                               + element.nodeValue.substr(index+1);
        !           229:           }
        !           230:           break;
1.1       albertel  231:       }
1.2     ! albertel  232:     }
        !           233:     return element;
        !           234:   },
1.1       albertel  235: 
1.2     ! albertel  236:   /*
        !           237:    *  If a matching end tag has been found, process the mathematics.
        !           238:    *  Otherwise, update the search data for the given delimiter,
        !           239:    *  or ignore it, as the item dictates.
        !           240:    */
        !           241:   customProcessMatch: function (match,index,element) {
        !           242:     if (match == this.search.end) {
        !           243:       this.search.close = element;
        !           244:       this.search.clength = match.length;
        !           245:       this.search.cpos = this.pattern.lastIndex;
        !           246:       this.search.matched = 1;
        !           247:     } else if (match == this.inLineOpen) {
        !           248:       if (this.search.matched) {element = this.EncloseMath(element)}
        !           249:       this.ScanMark('span',element,this.inLineClose);
        !           250:     } else if (match == this.displayOpen) {
        !           251:       if (this.search.matched) {element = this.EncloseMath(element)}
        !           252:       this.ScanMark('div',element,this.displayClose);
        !           253:     }
        !           254:     return element;
        !           255:   },
        !           256: 
        !           257:   /*
        !           258:    *  Return a structure that records the starting location
        !           259:    *  for the math element, and the end delimiter we want to find.
        !           260:    */
        !           261:   ScanMark: function (type,element,end) {
        !           262:     var len = RegExp.$1.length;
        !           263:     this.search = {
        !           264:       type: type, end: end, open: element, olength: len,
        !           265:       pos: this.pattern.lastIndex - len
        !           266:     };
        !           267:   },
        !           268:   
        !           269:   /*******************************************************************/
1.1       albertel  270: 
1.2     ! albertel  271:   /*
        !           272:    *  Surround the mathematics by an appropriate
        !           273:    *  SPAN or DIV element marked as CLASS="math".
        !           274:    */
        !           275:   EncloseMath: function (element) {
        !           276:     if (this.callback) {if (!this.callback()) {return null}}
        !           277:     var search = this.search;
        !           278:     var close = search.close;
        !           279:     if (search.cpos == close.length) {close = close.nextSibling}
        !           280:        else {close = close.splitText(search.cpos)}
        !           281:     if (!close) {close = jsMath.document.createTextNode("")}
        !           282:     if (element == search.close) {element = close}
        !           283:     var math = search.open.splitText(search.pos);
        !           284:     while (math.nextSibling && math.nextSibling != close) {
        !           285:       if (math.nextSibling.nodeValue) {math.nodeValue += math.nextSibling.nodeValue}
        !           286:         else {math.nodeValue += ' '}
        !           287:       math.parentNode.removeChild(math.nextSibling);
        !           288:     }
        !           289:     var TeX = math.nodeValue.substr(search.olength,
        !           290:       math.nodeValue.length-search.olength-search.clength);
        !           291:     math.parentNode.removeChild(math);
        !           292:     math = this.createMathTag(search.type,TeX);
        !           293:     //
        !           294:     //  This is where older, buggy browsers can fail under unpredicatble 
        !           295:     //  circumstances, so we trap errors and at least get to continue
        !           296:     //  with the rest of the math.  (## should add error message ##)
        !           297:     //
        !           298:     try {
1.1       albertel  299:       if (close && close.parentNode) {
                    300:         close.parentNode.insertBefore(math,close);
                    301:       } else if (search.open.nextSibling) {
                    302:         search.open.parentNode.insertBefore(math,search.open.nextSibling);
                    303:       } else {
                    304:         search.open.parentNode.appendChild(math);
                    305:       }
1.2     ! albertel  306:     } catch (err) {}
        !           307:     this.search = {}; this.pattern.lastIndex = 0;
        !           308:     return math;
        !           309:   },
1.1       albertel  310:     
1.2     ! albertel  311:   /*
        !           312:    *  Create an element for the mathematics
        !           313:    */
        !           314:   createMathTag: function (type,text) {
        !           315:     var tag = jsMath.document.createElement(type); tag.className = "math";
        !           316:     var math = jsMath.document.createTextNode(text);
        !           317:     tag.appendChild(math);
        !           318:     return tag;
        !           319:   },
        !           320: 
        !           321:   //
        !           322:   //  MSIE won't let you insert a DIV within tags that are supposed to
        !           323:   //  contain in-line data (like <P> or <SPAN>), so we have to fake it
        !           324:   //  using SPAN tags that force the formatting to work like DIV.  We
        !           325:   //  use a separate SPAN that is the full width of the containing
        !           326:   //  item, and that has the margins and centering from the div.typeset
        !           327:   //  style.
        !           328:   //
        !           329:   MSIEcreateMathTag: function (type,text) {
        !           330:     var tag = jsMath.document.createElement("span");
        !           331:     tag.className = "math";
        !           332:     text = text.replace(/</g,'&lt;').replace(/>/g,'&gt;');
        !           333:     if (type == 'div') {
        !           334:       tag.className = "";
        !           335:       tag.style.width = "100%"; tag.style.margin = jsMath.tex2math.margin;
        !           336:       tag.style.display = "inline-block";
        !           337:       text = '<span class="math">\\displaystyle{'+text+'}</span>';
        !           338:       if (jsMath.tex2math.center) {
        !           339:         tag.style.textAlign = "center";
        !           340:         text = '<span style="text-align:left">'+text+'</span>'
        !           341:       }
        !           342:     }
        !           343:     tag.innerHTML = text;
        !           344:     return tag;
        !           345:   },
        !           346:   
        !           347:   /*******************************************************************/
        !           348: 
        !           349:   Init: function () {
        !           350: 
        !           351:     if (this.inited || !jsMath.browser) return
1.1       albertel  352:     /*
1.2     ! albertel  353:      *  MSIE can't handle the DIV's properly, so we need to do it by
        !           354:      *  hand.  Look up the style for typeset math to see if the user
        !           355:      *  has changed it, and get whether it is centered or indented
        !           356:      *  so we can mirror that using a SPAN
1.1       albertel  357:      */
1.2     ! albertel  358:     if (jsMath.browser == 'MSIE' && navigator.platform == 'Win32') {
        !           359:       this.createMathTag = this.MSIEcreateMathTag;
        !           360:       this.margin = ""; this.center = 0;
        !           361:       for (var i = 0; i < jsMath.document.styleSheets.length; i++) {
        !           362:         var rules = jsMath.document.styleSheets[i].cssRules;
        !           363:         if (!rules) {rules = jsMath.document.styleSheets[i].rules}
        !           364:         for (var j = 0; j < rules.length; j++) {
        !           365:           if (rules[j].selectorText.toLowerCase() == 'div.typeset') {
        !           366:             if (rules[j].style.margin != "") {this.margin = rules[j].style.margin}
        !           367:             this.center = (rules[j].style.textAlign == 'center');
        !           368:           }
        !           369:         }
1.1       albertel  370:       }
                    371:     }
1.2     ! albertel  372:     this.inited = 1;
        !           373:   },
        !           374:   
        !           375:   /*
        !           376:    *  Test to see if we need to override the pattern exec() call
        !           377:    *  (for MSIE on the Mac).
        !           378:    */
        !           379:   TestPatterns: function () {
        !           380:     var pattern = /a/g;
        !           381:     var match = pattern.exec("xax");
        !           382:     this.fixPatterns = (pattern.lastIndex != 2 && match.lastIndex == 2);
1.1       albertel  383:   }
1.2     ! albertel  384:   
1.1       albertel  385: });
                    386: 
                    387: /*
1.2     ! albertel  388:  *  Initialize
1.1       albertel  389:  */
                    390: if (jsMath.Controls.cookie.tex2math == null) {jsMath.Controls.cookie.tex2math = 1}
                    391: if (jsMath.tex2math.allowDisableTag == null) {jsMath.tex2math.allowDisableTag = 1}
1.2     ! albertel  392: jsMath.tex2math.TestPatterns();
        !           393: jsMath.tex2math.createPattern('stdPattern',/(\\[\(\)\[\]$]|\$\$|\$)/g);
1.1       albertel  394: 
                    395: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>