4 * Part of the jsMath package for mathematics on the web.
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.
11 * ---------------------------------------------------------------------
13 * Copyright 2004-2007 by Davide P. Cervone
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
19 * http://www.apache.org/licenses/LICENSE-2.0
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.
28 if (!jsMath.tex2math) {jsMath.tex2math = {}} // make sure jsMath.tex2math is defined
29 if (!jsMath.tex2math.loaded) { // only load it once
31 if (!jsMath.Controls) {jsMath.Controls = {}}
32 if (!jsMath.Controls.cookie) {jsMath.Controls.cookie = {}}
34 jsMath.Add(jsMath.tex2math,{
40 * Call the main conversion routine with appropriate flags
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
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
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
70 ConvertCustom: function (element) {
71 this.Convert(element,{custom: 1, fixEscapedDollars: 0});
74 /*******************************************************************/
77 * Define a custom search by indicating the
78 * strings to use for starting and ending
79 * in-line and display mathematics
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'
92 patternQuote: function (s) {
93 s = s.replace(/([\^$(){}+*?\-|\[\]\:\\])/g,'\\$1');
98 * MSIE on the Mac doesn't handle lastIndex correctly, so
99 * override it and implement it correctly.
101 createPattern: function (name,pattern) {
102 jsMath.tex2math[name] = pattern;
103 if (this.fixPatterns) {
104 pattern.oldExec = pattern.exec;
105 pattern.exec = this.msiePatternExec;
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}
116 /*******************************************************************/
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'.
123 Convert: function (element,flags) {
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]}
131 this.pattern = this.customPattern;
132 this.ProcessMatch = this.customProcessMatch;
134 this.pattern = this.stdPattern;
135 this.ProcessMatch = this.stdProcessMatch;
137 if (this.processDoubleDollars || this.processSingleDollars ||
138 this.processSlashParens || this.processSlashBrackets ||
139 this.processLaTeXenvironments || this.custom) this.ScanElement(element);
144 * Recursively look through a document for text nodes that could
145 * contain mathematics.
147 ScanElement: function (element,ignore) {
148 if (!element) {element = jsMath.document.body}
149 if (typeof(element) == 'string') {element = jsMath.document.getElementById(element)}
151 if (element.nodeName == '#text') {
152 if (!ignore) {element = this.ScanText(element)}
154 if (element.className == null) {element.className = ''}
155 if (element.firstChild && element.className != 'math') {
156 var off = ignore || element.className.match(/(^| )tex2math_ignore( |$)/) ||
157 (element.tagName && element.tagName.match(/^(script|noscript|style|textarea|pre|code)$/i));
158 off = off && !element.className.match(/(^| )tex2math_process( |$)/);
159 this.ScanElement(element.firstChild,off);
162 if (element) {element = element.nextSibling}
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).
172 ScanText: function (element) {
173 if (element.nodeValue.replace(/\s+/,'') == '') {return element}
174 var match; var prev; this.search = {};
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);
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 element.nodeName.toLowerCase() == "#comment"))
187 {prev = element; element = element.nextSibling}
188 if (!element || element.nodeName != '#text') {return prev}
194 * If a matching end tag has been found, process the mathematics.
195 * Otherwise, update the search data for the given delimiter,
196 * or ignore it, as the item dictates.
198 stdProcessMatch: function (match,index,element) {
199 if (match == this.search.end) {
200 this.search.close = element;
201 this.search.cpos = this.pattern.lastIndex;
202 this.search.clength = (match.substr(0,4) == '\\end' ? 0 : match.length);
203 element = this.EncloseMath(element);
207 if ((this.search.end == null ||
208 (this.search.end != '$' && this.search.end != '$$')) &&
209 this.processSlashParens) {
210 this.ScanMark('span',element,'\\)');
215 if ((this.search.end == null ||
216 (this.search.end != '$' && this.search.end != '$$')) &&
217 this.processSlashBrackets) {
218 this.ScanMark('div',element,'\\]');
223 if (this.processDoubleDollars) {
224 var type = (this.doubleDollarsAreInLine? 'span': 'div');
225 this.ScanMark(type,element,'$$');
230 if (this.search.end == null && this.processSingleDollars) {
231 this.ScanMark('span',element,'$');
236 if (this.search.end == null && this.fixEscapedDollars) {
237 element.nodeValue = element.nodeValue.substr(0,index)
238 + element.nodeValue.substr(index+1);
239 this.pattern.lastIndex--;
247 if (match.substr(0,6) == '\\begin' && this.search.end == null &&
248 this.processLaTeXenvironments) {
249 this.ScanMark('div',element,'\\end'+match.substr(6));
250 this.search.olength = 0;
259 * If a matching end tag has been found, process the mathematics.
260 * Otherwise, update the search data for the given delimiter,
261 * or ignore it, as the item dictates.
263 customProcessMatch: function (match,index,element) {
264 if (match == this.search.end) {
265 this.search.close = element;
266 this.search.clength = match.length;
267 this.search.cpos = this.pattern.lastIndex;
268 if (match == this.inLineOpen || match == this.displayOpen) {
269 element = this.EncloseMath(element);
270 } else {this.search.matched = 1}
271 } else if (match == this.inLineOpen) {
272 if (this.search.matched) {element = this.EncloseMath(element)}
273 this.ScanMark('span',element,this.inLineClose);
274 } else if (match == this.displayOpen) {
275 if (this.search.matched) {element = this.EncloseMath(element)}
276 this.ScanMark('div',element,this.displayClose);
282 * Return a structure that records the starting location
283 * for the math element, and the end delimiter we want to find.
285 ScanMark: function (type,element,end) {
286 var len = this.pattern.match[1].length;
288 type: type, end: end, open: element, olength: len,
289 pos: this.pattern.lastIndex - len
293 /*******************************************************************/
296 * Surround the mathematics by an appropriate
297 * SPAN or DIV element marked as CLASS="math".
299 EncloseMath: function (element) {
300 var search = this.search; search.end = null;
301 var close = search.close;
302 if (search.cpos == close.length) {close = close.nextSibling}
303 else {close = close.splitText(search.cpos)}
304 if (!close) {close = jsMath.document.createTextNode("")}
305 if (element == search.close) {element = close}
306 var math = search.open.splitText(search.pos);
307 while (math.nextSibling && math.nextSibling != close) {
308 if (math.nextSibling.nodeValue !== null) {
309 if (math.nextSibling.nodeName.toLowerCase() === "#comment") {
310 math.nodeValue += math.nextSibling.nodeValue.replace(/^\[CDATA\[(.*)\]\]$/,"$1");
312 math.nodeValue += math.nextSibling.nodeValue;
315 math.nodeValue += ' ';
317 math.parentNode.removeChild(math.nextSibling);
319 var TeX = math.nodeValue.substr(search.olength,
320 math.nodeValue.length-search.olength-search.clength);
321 math.parentNode.removeChild(math);
322 math = this.createMathTag(search.type,TeX);
324 // This is where older, buggy browsers can fail under unpredicatble
325 // circumstances, so we trap errors and at least get to continue
326 // with the rest of the math. (## should add error message ##)
329 if (close && close.parentNode) {
330 close.parentNode.insertBefore(math,close);
331 } else if (search.open.nextSibling) {
332 search.open.parentNode.insertBefore(math,search.open.nextSibling);
334 search.open.parentNode.appendChild(math);
337 this.search = {}; this.pattern.lastIndex = 0;
342 * Create an element for the mathematics
344 createMathTag: function (type,text) {
345 var tag = jsMath.document.createElement(type); tag.className = "math";
346 var math = jsMath.document.createTextNode(text);
347 tag.appendChild(math);
352 // MSIE won't let you insert a DIV within tags that are supposed to
353 // contain in-line data (like <P> or <SPAN>), so we have to fake it
354 // using SPAN tags that force the formatting to work like DIV. We
355 // use a separate SPAN that is the full width of the containing
356 // item, and that has the margins and centering from the div.typeset
359 MSIEcreateMathTag: function (type,text) {
360 var tag = jsMath.document.createElement("span");
361 tag.className = "math";
362 text = text.replace(/</g,'<').replace(/>/g,'>');
364 tag.className = "tex2math_div";
365 text = '<span class="math">\\displaystyle{'+text+'}</span>';
367 tag.innerHTML = text;
371 /*******************************************************************/
374 if (!jsMath.browser && document.all && !window.opera) {
375 jsMath.browser = 'MSIE';
376 jsMath.platform = (navigator.platform.match(/Mac/) ? "mac" :
377 navigator.platform.match(/Win/) ? "pc" : "unix");
379 if (this.inited || !jsMath.browser) return;
381 * MSIE can't handle the DIV's properly, so we need to do it by
382 * hand. Use an extra SPAN that uses CSS to act like a DIV.
384 if (jsMath.browser == 'MSIE' && jsMath.platform == 'pc')
385 {this.createMathTag = this.MSIEcreateMathTag}
390 * Test to see if we need to override the pattern exec() call
391 * (for MSIE on the Mac).
393 TestPatterns: function () {
395 var match = pattern.exec("xax");
396 this.fixPatterns = (pattern.lastIndex != 2 && match.lastIndex == 2);
404 if (jsMath.Controls.cookie.tex2math == null) {jsMath.Controls.cookie.tex2math = 1}
405 if (jsMath.tex2math.allowDisableTag == null) {jsMath.tex2math.allowDisableTag = 1}
406 jsMath.tex2math.TestPatterns();
407 jsMath.tex2math.createPattern('stdPattern',/(\\[\(\)\[\]$\\]|\$\$|\$|\\(begin|end)\{[^}]+\})/g);