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

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) {
1.3     ! albertel  148:       if (element.className == null) {element.className = ''}
1.2       albertel  149:       if (element.nodeName == '#text') {
                    150:         if (!ignore) {element = this.ScanText(element)}
                    151:       } else if (element.firstChild && element.className != 'math') {
                    152:         var off = ignore || element.className == 'tex2math_ignore' ||
                    153:            (element.tagName && element.tagName.match(/^(script|noscript|style|textarea|pre)$/i));
                    154:         off = off && element.className != 'tex2math_process';
                    155:         this.ScanElement(element.firstChild,off);
1.1       albertel  156:       }
1.2       albertel  157:       if (element) {element = element.nextSibling}
                    158:     }
                    159:   },
                    160:   
                    161:   /*
                    162:    *  Looks through a text element for math delimiters and
                    163:    *  process them.  If <BR> tags are found in the middle, they
                    164:    *  are ignored (this is for BBS systems that have editors
                    165:    *  that insert these automatically).
                    166:    */
                    167:   ScanText: function (element) {
                    168:     if (element.nodeValue.replace(/\s+/,'') == '') {return element}
                    169:     var match; var prev; this.search = {};
                    170:     while (element) {
                    171:       this.pattern.lastIndex = 0;
                    172:       while (element && element.nodeName == '#text' &&
                    173:             (match = this.pattern.exec(element.nodeValue))) {
1.3     ! albertel  174:         this.pattern.match = match;
1.2       albertel  175:         element = this.ProcessMatch(match[0],match.index,element);
1.1       albertel  176:       }
1.2       albertel  177:       if (this.search.matched) {element = this.EncloseMath(element)}
                    178:       if (!element) {return null}
                    179:       prev = element; element = element.nextSibling;
                    180:       while (element && element.nodeName.toLowerCase() == 'br')
                    181:         {prev = element; element = element.nextSibling}
                    182:       if (!element || element.nodeName != '#text') {return prev}
                    183:     }
                    184:     return element;
                    185:   },
                    186:   
                    187:   /*
                    188:    *  If a matching end tag has been found, process the mathematics.
                    189:    *  Otherwise, update the search data for the given delimiter,
                    190:    *  or ignore it, as the item dictates.
                    191:    */
                    192:   stdProcessMatch: function (match,index,element) {
                    193:     if (match == this.search.end) {
                    194:       this.search.close = element;
                    195:       this.search.clength = match.length;
                    196:       this.search.cpos = this.pattern.lastIndex;
                    197:       element = this.EncloseMath(element);
                    198:     } else {
                    199:       switch (match) {
                    200:         case '\\(':
1.3     ! albertel  201:           if (this.search.end == null ||
        !           202:              (this.search.end != '$' && this.search.end != '$$') &&
1.2       albertel  203:               this.processSlashParens) {
                    204:             this.ScanMark('span',element,'\\)');
                    205:           }
                    206:           break;
                    207: 
                    208:         case '\\[':
1.3     ! albertel  209:           if (this.search.end == null ||
        !           210:              (this.search.end != '$' && this.search.end != '$$') &&
1.2       albertel  211:               this.processSlashBrackets) {
                    212:             this.ScanMark('div',element,'\\]');
                    213:           }
                    214:           break;
                    215: 
                    216:         case '$$':
                    217:           if (this.processDoubleDollars) {
                    218:             var type = (this.doubleDollarsAreInLine? 'span': 'div');
                    219:             this.ScanMark(type,element,'$$');
                    220:           }
                    221:           break;
                    222: 
                    223:         case '$':
                    224:           if (this.search.end == null && this.processSingleDollars) {
                    225:             this.ScanMark('span',element,'$');
                    226:           }
                    227:           break;
                    228: 
                    229:         case '\\$':
                    230:           if (this.search.end == null && this.fixEscapedDollars) {
                    231:             element.nodeValue = element.nodeValue.substr(0,index)
                    232:                               + element.nodeValue.substr(index+1);
                    233:           }
                    234:           break;
1.1       albertel  235:       }
1.2       albertel  236:     }
                    237:     return element;
                    238:   },
1.1       albertel  239: 
1.2       albertel  240:   /*
                    241:    *  If a matching end tag has been found, process the mathematics.
                    242:    *  Otherwise, update the search data for the given delimiter,
                    243:    *  or ignore it, as the item dictates.
                    244:    */
                    245:   customProcessMatch: function (match,index,element) {
                    246:     if (match == this.search.end) {
                    247:       this.search.close = element;
                    248:       this.search.clength = match.length;
                    249:       this.search.cpos = this.pattern.lastIndex;
                    250:       this.search.matched = 1;
                    251:     } else if (match == this.inLineOpen) {
                    252:       if (this.search.matched) {element = this.EncloseMath(element)}
                    253:       this.ScanMark('span',element,this.inLineClose);
                    254:     } else if (match == this.displayOpen) {
                    255:       if (this.search.matched) {element = this.EncloseMath(element)}
                    256:       this.ScanMark('div',element,this.displayClose);
                    257:     }
                    258:     return element;
                    259:   },
                    260: 
                    261:   /*
                    262:    *  Return a structure that records the starting location
                    263:    *  for the math element, and the end delimiter we want to find.
                    264:    */
                    265:   ScanMark: function (type,element,end) {
1.3     ! albertel  266:     var len = this.pattern.match[1].length;
1.2       albertel  267:     this.search = {
                    268:       type: type, end: end, open: element, olength: len,
                    269:       pos: this.pattern.lastIndex - len
                    270:     };
                    271:   },
                    272:   
                    273:   /*******************************************************************/
1.1       albertel  274: 
1.2       albertel  275:   /*
                    276:    *  Surround the mathematics by an appropriate
                    277:    *  SPAN or DIV element marked as CLASS="math".
                    278:    */
                    279:   EncloseMath: function (element) {
                    280:     if (this.callback) {if (!this.callback()) {return null}}
                    281:     var search = this.search;
                    282:     var close = search.close;
                    283:     if (search.cpos == close.length) {close = close.nextSibling}
                    284:        else {close = close.splitText(search.cpos)}
                    285:     if (!close) {close = jsMath.document.createTextNode("")}
                    286:     if (element == search.close) {element = close}
                    287:     var math = search.open.splitText(search.pos);
                    288:     while (math.nextSibling && math.nextSibling != close) {
                    289:       if (math.nextSibling.nodeValue) {math.nodeValue += math.nextSibling.nodeValue}
                    290:         else {math.nodeValue += ' '}
                    291:       math.parentNode.removeChild(math.nextSibling);
                    292:     }
                    293:     var TeX = math.nodeValue.substr(search.olength,
                    294:       math.nodeValue.length-search.olength-search.clength);
                    295:     math.parentNode.removeChild(math);
                    296:     math = this.createMathTag(search.type,TeX);
                    297:     //
                    298:     //  This is where older, buggy browsers can fail under unpredicatble 
                    299:     //  circumstances, so we trap errors and at least get to continue
                    300:     //  with the rest of the math.  (## should add error message ##)
                    301:     //
                    302:     try {
1.1       albertel  303:       if (close && close.parentNode) {
                    304:         close.parentNode.insertBefore(math,close);
                    305:       } else if (search.open.nextSibling) {
                    306:         search.open.parentNode.insertBefore(math,search.open.nextSibling);
                    307:       } else {
                    308:         search.open.parentNode.appendChild(math);
                    309:       }
1.2       albertel  310:     } catch (err) {}
                    311:     this.search = {}; this.pattern.lastIndex = 0;
                    312:     return math;
                    313:   },
1.1       albertel  314:     
1.2       albertel  315:   /*
                    316:    *  Create an element for the mathematics
                    317:    */
                    318:   createMathTag: function (type,text) {
                    319:     var tag = jsMath.document.createElement(type); tag.className = "math";
                    320:     var math = jsMath.document.createTextNode(text);
                    321:     tag.appendChild(math);
                    322:     return tag;
                    323:   },
                    324: 
                    325:   //
                    326:   //  MSIE won't let you insert a DIV within tags that are supposed to
                    327:   //  contain in-line data (like <P> or <SPAN>), so we have to fake it
                    328:   //  using SPAN tags that force the formatting to work like DIV.  We
                    329:   //  use a separate SPAN that is the full width of the containing
                    330:   //  item, and that has the margins and centering from the div.typeset
                    331:   //  style.
                    332:   //
                    333:   MSIEcreateMathTag: function (type,text) {
                    334:     var tag = jsMath.document.createElement("span");
                    335:     tag.className = "math";
                    336:     text = text.replace(/</g,'&lt;').replace(/>/g,'&gt;');
                    337:     if (type == 'div') {
                    338:       tag.className = "";
                    339:       tag.style.width = "100%"; tag.style.margin = jsMath.tex2math.margin;
                    340:       tag.style.display = "inline-block";
                    341:       text = '<span class="math">\\displaystyle{'+text+'}</span>';
                    342:       if (jsMath.tex2math.center) {
                    343:         tag.style.textAlign = "center";
                    344:         text = '<span style="text-align:left">'+text+'</span>'
                    345:       }
                    346:     }
                    347:     tag.innerHTML = text;
                    348:     return tag;
                    349:   },
                    350:   
                    351:   /*******************************************************************/
                    352: 
                    353:   Init: function () {
                    354: 
                    355:     if (this.inited || !jsMath.browser) return
1.1       albertel  356:     /*
1.2       albertel  357:      *  MSIE can't handle the DIV's properly, so we need to do it by
                    358:      *  hand.  Look up the style for typeset math to see if the user
                    359:      *  has changed it, and get whether it is centered or indented
                    360:      *  so we can mirror that using a SPAN
1.1       albertel  361:      */
1.2       albertel  362:     if (jsMath.browser == 'MSIE' && navigator.platform == 'Win32') {
                    363:       this.createMathTag = this.MSIEcreateMathTag;
                    364:       this.margin = ""; this.center = 0;
                    365:       for (var i = 0; i < jsMath.document.styleSheets.length; i++) {
                    366:         var rules = jsMath.document.styleSheets[i].cssRules;
                    367:         if (!rules) {rules = jsMath.document.styleSheets[i].rules}
                    368:         for (var j = 0; j < rules.length; j++) {
                    369:           if (rules[j].selectorText.toLowerCase() == 'div.typeset') {
                    370:             if (rules[j].style.margin != "") {this.margin = rules[j].style.margin}
                    371:             this.center = (rules[j].style.textAlign == 'center');
                    372:           }
                    373:         }
1.1       albertel  374:       }
                    375:     }
1.2       albertel  376:     this.inited = 1;
                    377:   },
                    378:   
                    379:   /*
                    380:    *  Test to see if we need to override the pattern exec() call
                    381:    *  (for MSIE on the Mac).
                    382:    */
                    383:   TestPatterns: function () {
                    384:     var pattern = /a/g;
                    385:     var match = pattern.exec("xax");
                    386:     this.fixPatterns = (pattern.lastIndex != 2 && match.lastIndex == 2);
1.1       albertel  387:   }
1.2       albertel  388:   
1.1       albertel  389: });
                    390: 
                    391: /*
1.2       albertel  392:  *  Initialize
1.1       albertel  393:  */
                    394: if (jsMath.Controls.cookie.tex2math == null) {jsMath.Controls.cookie.tex2math = 1}
                    395: if (jsMath.tex2math.allowDisableTag == null) {jsMath.tex2math.allowDisableTag = 1}
1.2       albertel  396: jsMath.tex2math.TestPatterns();
                    397: jsMath.tex2math.createPattern('stdPattern',/(\\[\(\)\[\]$]|\$\$|\$)/g);
1.1       albertel  398: 
                    399: }

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