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