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

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

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