File:  [LON-CAPA] / loncom / html / adm / jsMath / plugins / tex2math.js
Revision 1.4: download - view: text, annotated - select for diffs
Tue Oct 9 21:29:28 2007 UTC (16 years, 9 months ago) by albertel
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_99_0, version_2_9_1, version_2_9_0, version_2_8_X, version_2_8_99_1, version_2_8_99_0, version_2_8_2, version_2_8_1, version_2_8_0, version_2_7_X, version_2_7_99_1, version_2_7_99_0, version_2_7_1, version_2_7_0, version_2_6_X, version_2_6_99_1, version_2_6_99_0, version_2_6_3, version_2_6_2, version_2_6_1, version_2_6_0, version_2_5_99_1, version_2_5_99_0, version_2_12_X, version_2_11_X, version_2_11_5, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0_RC1, version_2_11_0, version_2_10_X, version_2_10_1, version_2_10_0_RC2, version_2_10_0_RC1, version_2_10_0, loncapaMITrelate_1, language_hyphenation_merge, language_hyphenation, bz6209-base, bz6209, bz5969, bz2851, PRINT_INCOMPLETE_base, PRINT_INCOMPLETE, HEAD, GCI_3, GCI_2, GCI_1, BZ5971-printing-apage, BZ5434-fox, BZ4492-merge, BZ4492-feature_horizontal_radioresponse
- jsMath 3.4e

    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 within 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:  *
   13:  *  Copyright 2004-2007 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.
   26:  */
   27: 
   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,{
   35:   
   36:   loaded: 1,
   37:   window: window,
   38: 
   39:   /*
   40:    *  Call the main conversion routine with appropriate flags
   41:    */
   42: 
   43:   ConvertTeX: function (element) {
   44:     this.Convert(element,{
   45:       processSingleDollars: 1, processDoubleDollars: 1,
   46:       processSlashParens: 1, processSlashBrackets: 1,
   47:       processLaTeXenvironments: 0,
   48:       custom: 0, fixEscapedDollars: 1
   49:     });
   50:   },
   51:   
   52:   ConvertTeX2: function (element) {
   53:     this.Convert(element,{
   54:       processSingleDollars: 0, processDoubleDollars: 1,
   55:       processSlashParens: 1, processSlashBrackets: 1,
   56:       processLaTeXenvironments: 0,
   57:       custom: 0, fixEscapedDollars: 0
   58:     });
   59:   },
   60:   
   61:   ConvertLaTeX: function (element) {
   62:     this.Convert(element,{
   63:       processSingleDollars: 0, processDoubleDollars: 0,
   64:       processSlashParens: 1, processSlashBrackets: 1,
   65:       processLaTeXenvironments: 1,
   66:       custom: 0, fixEscapedDollars: 0
   67:     });
   68:   },
   69:   
   70:   ConvertCustom: function (element) {
   71:     this.Convert(element,{custom: 1, fixEscapedDollars: 0});
   72:   },
   73:   
   74:   /*******************************************************************/
   75: 
   76:   /*
   77:    *  Define a custom search by indicating the
   78:    *  strings to use for starting and ending
   79:    *  in-line and display mathematics
   80:    */
   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) {
   93:     s = s.replace(/([\^$(){}+*?\-|\[\]\:\\])/g,'\\$1');
   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:   },
  115: 
  116:   /*******************************************************************/
  117: 
  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;
  136:       }
  137:       if (this.processDoubleDollars || this.processSingleDollars ||
  138:           this.processSlashParens   || this.processSlashBrackets ||
  139:           this.processLaTeXenvironments || this.custom) this.ScanElement(element);
  140:     }
  141:   },
  142: 
  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)}
  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:         }
  161:       }
  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))) {
  179:         this.pattern.match = match;
  180:         element = this.ProcessMatch(match[0],match.index,element);
  181:       }
  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;
  201:       this.search.clength = (match.substr(0,4) == '\\end' ? 0 : match.length);
  202:       element = this.EncloseMath(element);
  203:     } else {
  204:       switch (match) {
  205:         case '\\(':
  206:           if ((this.search.end == null ||
  207:              (this.search.end != '$' && this.search.end != '$$')) &&
  208:               this.processSlashParens) {
  209:             this.ScanMark('span',element,'\\)');
  210:           }
  211:           break;
  212: 
  213:         case '\\[':
  214:           if ((this.search.end == null ||
  215:              (this.search.end != '$' && this.search.end != '$$')) &&
  216:                this.processSlashBrackets) {
  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);
  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;
  247:           }
  248:           break;
  249:       }
  250:     }
  251:     return element;
  252:   },
  253: 
  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;
  264:       if (match == this.inLineOpen || match == this.displayOpen) {
  265:         element = this.EncloseMath(element);
  266:       } else {this.search.matched = 1}
  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) {
  282:     var len = this.pattern.match[1].length;
  283:     this.search = {
  284:       type: type, end: end, open: element, olength: len,
  285:       pos: this.pattern.lastIndex - len
  286:     };
  287:   },
  288:   
  289:   /*******************************************************************/
  290: 
  291:   /*
  292:    *  Surround the mathematics by an appropriate
  293:    *  SPAN or DIV element marked as CLASS="math".
  294:    */
  295:   EncloseMath: function (element) {
  296:     var search = this.search; search.end = null;
  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 {
  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:       }
  325:     } catch (err) {}
  326:     this.search = {}; this.pattern.lastIndex = 0;
  327:     return math;
  328:   },
  329:     
  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') {
  353:       tag.className = "tex2math_div";
  354:       text = '<span class="math">\\displaystyle{'+text+'}</span>';
  355:     }
  356:     tag.innerHTML = text;
  357:     return tag;
  358:   },
  359:   
  360:   /*******************************************************************/
  361: 
  362:   Init: function () {
  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;
  369:     /*
  370:      *  MSIE can't handle the DIV's properly, so we need to do it by
  371:      *  hand.  Use an extra SPAN that uses CSS to act like a DIV.
  372:      */
  373:     if (jsMath.browser == 'MSIE' && jsMath.platform == 'pc')
  374:       {this.createMathTag = this.MSIEcreateMathTag}
  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);
  386:   }
  387:   
  388: });
  389: 
  390: /*
  391:  *  Initialize
  392:  */
  393: if (jsMath.Controls.cookie.tex2math == null) {jsMath.Controls.cookie.tex2math = 1}
  394: if (jsMath.tex2math.allowDisableTag == null) {jsMath.tex2math.allowDisableTag = 1}
  395: jsMath.tex2math.TestPatterns();
  396: jsMath.tex2math.createPattern('stdPattern',/(\\[\(\)\[\]$]|\$\$|\$|\\(begin|end)\{[^}]+\})/g);
  397: 
  398: }

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