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,'<').replace(/>/g,'>');
! 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>