added jsMath files
[lambda.git] / jsMath / plugins / autoload.js
1 /*
2  *  autoload.js
3  *  
4  *  Part of the jsMath package for mathematics on the web.
5  *
6  *  This file is a plugin that checks if a page contains any math
7  *  that must be processed by jsMath, and only loads jsMath.js
8  *  when there is.
9  *  
10  *  You can control the items to look for via the variables
11  *  
12  *      jsMath.Autoload.findTeXstrings
13  *      jsMath.Autoload.findLaTeXstrings
14  *      jsMath.Autoload.findCustomStrings
15  *      jsMath.Autoload.findCustomSettings
16  *  
17  *  which control whether to look for TeX strings that will be converted
18  *  by jsMath.ConvertTeX(), or LaTeX strings that will be converted by
19  *  jsMath.ConvertLaTeX().  By default, the first is true and the second
20  *  and third are false.  The findCustomStrings can be used to specify your
21  *  own delimiters for in-line and display mathematics, e.g.
22  *  
23  *      jsMath.Autoload.findCustomStrings = [
24  *         '[math],'[/math]',          // start and end in-line math
25  *         '[display]','[/display]'    // start and end display math
26  *      ];
27  *  
28  *  Finally, findCustomSettings can be set to an object reference whose
29  *  name:value pairs control the individual search settings for tex2math.  
30  *  (See the plugins/tex2math.js file for more details).
31  *  
32  *  If any math strings are found, jsMath.js will be loaded automatically, 
33  *  but not loaded otherwise.  If any of the last four are set and TeX math
34  *  strings are found, then plugins/tex2ath.js will be loaded
35  *  automatically.  jsMath.Autoload.needsJsMath will be set to true or
36  *  false depending on whether jsMath needed to be loaded.
37  *  
38  *  The value of jsMath.Autoload.checkElement controls the element to be
39  *  searched by the autoload plug-in.  If unset, the complete document will
40  *  be searched.  If set to a string, the element with that name will be
41  *  searched.  If set to a DOM object, that object and its children will
42  *  be searched.
43  *  
44  *  Finally, there are two additional parameters that control files to
45  *  be loaded after jsMath.js, should it be needed.  These are
46  *  
47  *      jsMath.Autoload.loadFonts
48  *      jsMath.Autoload.loadFiles
49  *  
50  *  If jsMath.js is loaded, the fonts contained in the loadFonts array
51  *  will be loaded, and the JavaScript files listed in the loadFiles array
52  *  will be run.  Relative URL's are loaded based from the URL containing
53  *  jsMath.js.
54  *  
55  *  The autoload plugin can be loaded in the document HEAD or in the BODY. 
56  *  If it is loaded in the HEAD, you will need to call jsMath.Autoload.Check()
57  *  at the end of the BODY (say in the window.onload handler) in order to
58  *  get it to check the page for math that needs to be tagged, otherwise load
59  *  the file at the bottom of the BODY and it will run the check automatically.
60  *
61  *  You can call jsMath.Autoload.Run() after the check has been performed
62  *  in order to call the appropriate tex2math routines for the given Autoload
63  *  settings.  You can call jsMath.Autoload.Run() even when jsMath isn't loaded.
64  *  
65  *  ---------------------------------------------------------------------
66  *
67  *  Copyright 2004-2006 by Davide P. Cervone
68  * 
69  *  Licensed under the Apache License, Version 2.0 (the "License");
70  *  you may not use this file except in compliance with the License.
71  *  You may obtain a copy of the License at
72  * 
73  *      http://www.apache.org/licenses/LICENSE-2.0
74  * 
75  *  Unless required by applicable law or agreed to in writing, software
76  *  distributed under the License is distributed on an "AS IS" BASIS,
77  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
78  *  See the License for the specific language governing permissions and
79  *  limitations under the License.
80  */
81
82 /*************************************************************************/
83
84 /*
85  *  Make sure jsMath.Autoload is available
86  */
87 if (!window.jsMath) {window.jsMath = {}}
88 if (jsMath.Autoload == null) {jsMath.Autoload = {}}
89 jsMath.Add = function (dst,src) {for (var id in src) {dst[id] = src[id]}};
90 jsMath.document = document; // tex2math needs this
91
92 jsMath.Add(jsMath.Autoload,{
93   
94   Script: {
95
96     request: null,  // XMLHttpRequest object (if we can get it)
97     iframe: null,   // the hidden iframe (if not)
98     operaXMLHttpRequestBug: (window.opera != null), // is Opera browser
99
100     /*
101      *  Get XMLHttpRequest object, if possible, and look up the URL root
102      *  (MSIE can't use xmlReuest to load local files, so avoid that)
103      */
104     Init: function () {
105       this.Root();
106       if (window.XMLHttpRequest) {
107         try {this.request = new XMLHttpRequest} catch (err) {}
108         // MSIE and FireFox3 can't use xmlRequest on local files,
109         // but we don't have jsMath.browser yet to tell, so use this check
110         if (this.request && window.location.protocol == "file:") {
111           try {
112             this.request.open("GET",jsMath.Autoload.root+"plugins/autoload.js",false);
113             this.request.send(null);
114           } catch (err) {
115             this.request = null;
116             //  Firefox3 has window.postMessage for inter-window communication. 
117             //  It can be used to handle the new file:// security model,
118             //  so set up the listener.
119             if (window.postMessage && window.addEventListener) {
120               this.mustPost = 1;
121               window.addEventListener("message",jsMath.Autoload.Post.Listener,false);
122             }
123           }
124         }
125       }
126       if (!this.request && window.ActiveXObject && !this.mustPost) {
127         var xml = ["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.5.0","MSXML2.XMLHTTP.4.0",
128                    "MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];
129         for (var i = 0; i < xml.length && !this.request; i++) {
130           try {this.request = new ActiveXObject(xml[i])} catch (err) {}
131         }
132       }
133     },
134
135     /*
136      *  Load an external JavaScript file
137      */
138     Load: function (url) {
139       if (this.request && !(this.operaXMLHttpRequestBug && url == 'jsMath.js')) {
140         setTimeout(function () {jsMath.Autoload.Script.xmlLoad(url)},1);
141       } else {
142         this.startLoad(url);
143       }
144     },
145
146     /*
147      *  Load an external JavaScript file via XMLHttpRequest
148      */
149     xmlLoad: function (url) {
150       try {
151         this.request.open("GET",jsMath.Autoload.root+url,false);
152         this.request.send(null);
153       } catch (err) {
154         throw Error("autoload: can't load the file '"+url+"'\n"
155             + "Message: "+err.message);
156       }
157       if (this.request.status && this.request.status >= 400) {
158         throw Error("autoload: can't load the file '"+url+"'\n"
159             + "Error status: "+this.request.status);
160       }
161       window.eval(this.request.responseText);
162       this.endLoad();
163     },
164
165     /*
166      *  Load an external JavaScript file via jsMath-autoload.html
167      */
168     startLoad: function (url) {
169       this.iframe = document.createElement('iframe');
170       this.iframe.style.visibility = 'hidden';
171       this.iframe.style.position = 'absolute';
172       this.iframe.style.width  = '0px';
173       this.iframe.style.height = '0px';
174       if (document.body.firstChild) {
175         document.body.insertBefore(this.iframe,document.body.firstChild);
176       } else {
177         document.body.appendChild(this.iframe);
178       }
179       this.url = url; setTimeout('jsMath.Autoload.Script.setURL()',100);
180     },
181     endLoad: function () {setTimeout('jsMath.Autoload.Script.AfterLoad()',1)},
182
183     /*
184      *  Use location.replace() to avoid browsers placing the file in
185      *  the history (and messing up the BACK button action).  Not
186      *  completely effective in Firefox 1.0.x.  Safari won't handle
187      *  replace() if the document is local (not sure if that's really
188      *  the issue, but that's the only time I see it).
189      */
190     setURL: function () {
191       if (this.mustPost) {
192         this.iframe.src = jsMath.Autoload.Post.startLoad(this.url,this.iframe);
193       } else {
194         var url = jsMath.Autoload.root+"jsMath-autoload.html";
195         var doc = this.iframe.contentDocument;
196         if (!doc && this.iframe.contentWindow) {doc = this.iframe.contentWindow.document}
197         if (navigator.vendor == "Apple Computer, Inc." &&
198             document.location.protocol == 'file:') {doc = null}
199         if (doc) {doc.location.replace(url)} else {this.iframe.src = url}
200       }
201     },
202
203     /*
204      *  Queue items that need to be postponed until jsMath has run
205      */
206     queue: [],
207     Push: function (name,data) {this.queue[this.queue.length] = [name,data]},
208     RunStack: function () {
209       if (this.tex2math) {jsMath.Autoload.Check2(); return}
210       for (var i = 0; i < this.queue.length; i++) {
211         var name = this.queue[i][0];
212         var data = this.queue[i][1];
213         if (data.length == 1) {jsMath[name](data[0])}
214           else {jsMath[name](data[0],data[1],data[2],data[3])}
215       }
216      this.queue = [];
217     },
218   
219     AfterLoad: function () {jsMath.Autoload.Script.RunStack()},
220
221     /*
222      *  Look up the jsMath root directory, if it is not already supplied
223      */
224     Root: function () {
225       if (jsMath.Autoload.root) return;
226       var script = document.getElementsByTagName('script');
227       if (script) {
228         for (var i = 0; i < script.length; i++) {
229           var src = script[i].src;
230           if (src && src.match('(^|/|\\\\)plugins/autoload.js$')) {
231             jsMath.Autoload.root = src.replace(/plugins\/autoload.js$/,'');
232             break;
233           }
234         }
235       }
236     }
237
238   },
239   
240   /*
241    *  Handle window.postMessage() events in Firefox3
242    */
243   Post: {
244     window: null,  // iframe we are ing to
245   
246     Listener: function (event) {
247       if (event.source != jsMath.Autoload.Post.window) return;
248       var domain = event.origin.replace(/^file:\/\//,'');
249       var ddomain = document.domain.replace(/^file:\/\//,'');
250       if (domain == null || domain == "" || domain == "null") {domain = "localhost"}
251       if (ddomain == null || ddomain == "" || ddomain == "null") {ddomain = "localhost"}
252       if (domain != ddomain || event.data.substr(0,6) != "jsMAL:") return;
253       var type = event.data.substr(6,3).replace(/ /g,'');
254       var message = event.data.substr(10);
255       if (jsMath.Autoload.Post.Commands[type]) (jsMath.Autoload.Post.Commands[type])(message);
256       // cancel event?
257     },
258   
259     /*
260      *  Commands that can be performed by the listener
261      */
262     Commands: {
263       SCR: function (message) {window.eval(message)},
264       ERR: function (message) {jsMath.Autoload.Script.endLoad()},
265       END: function (message) {jsMath.Autoload.Script.endLoad()}
266     },
267     
268     startLoad: function (url,iframe) {
269       this.window = iframe.contentWindow;
270       return jsMath.Autoload.root+"jsMath-loader-post.html?autoload="+url;
271     },
272   
273     endLoad: function () {
274       this.window = null;
275     }
276   },
277
278   
279   /**************************************************************/
280   
281   /*
282    *  Load tex2math first (so we can call its search functions
283    *  to look to see if anything needs to be turned into math)
284    *  if it is needed, otherwise go on to the second check.
285    */
286   Check: function () {
287     if (this.checked) return; this.checked = 1;
288     if ((this.findTeXstrings || this.findLaTeXstrings ||
289          this.findCustomStrings || this.findCustomSettings) &&
290          (!jsMath.tex2math || !jsMath.tex2math.loaded)) {
291       this.Script.tex2math = 1;
292       this.Script.Load('plugins/tex2math.js');
293     } else {
294       if (!jsMath.tex2math) {jsMath.tex2math = {}}
295       this.Check2();
296     }
297   },
298   ReCheck: function () {
299     if (jsMath.loaded) return;
300     this.InitStubs();
301     this.checked = 0;
302     this.Script.queue = [];
303     this.Check();
304   },
305
306   /*
307    *  Once tex2math is loaded, use it to check for math that
308    *  needs to be tagged for jsMath, and load jsMath if it is needed
309    */
310   Check2: function () {
311     this.Script.tex2math = 0; this.needsJsMath = 0;
312     if (this.checkElement == null) {this.checkElement = null}
313
314     if (this.findTeXstrings)     {jsMath.tex2math.ConvertTeX(this.checkElement)}
315     if (this.findLaTeXstrings)   {jsMath.tex2math.ConvertLaTeX(this.checkElement)}
316     if (this.findCustomSettings) {jsMath.tex2math.Convert(this.checkElement,this.findCustomSettings)}
317     if (this.findCustomStrings)  {
318       var s = this.findCustomStrings;
319       jsMath.tex2math.CustomSearch(s[0],s[1],s[2],s[3]);
320       jsMath.tex2math.ConvertCustom(this.checkElement);
321     }
322
323     this.needsJsMath = this.areMathElements(this.checkElement);
324     if (this.needsJsMath) {
325       this.LoadJsMath();
326     } else {
327       jsMath.Process = function () {};
328       jsMath.ProcessBeforeShowing = function () {};
329       jsMath.ProcessElement = function () {};
330       jsMath.ConvertTeX = function () {};
331       jsMath.ConvertTeX2 = function () {};
332       jsMath.ConvertLaTeX = function () {};
333       jsMath.ConvertCustom = function () {};
334       jsMath.CustomSearch = function () {};
335       jsMath.Macro = function () {};
336       jsMath.Synchronize = function (code,data) {
337         if (typeof(code) == 'string') {eval(code)} else {code(data)}
338       };
339       jsMath.Autoload.Script.RunStack(); // perform pending commands
340       jsMath.Autoload.setMessage();
341     }
342   },
343
344   /*
345    *  A callback used in the tex2math searches to signal that
346    *  some math has been found.
347    */
348   tex2mathCallback: function () {
349     jsMath.Autoload.needsJsMath = 1;
350     return false;
351   },
352
353   /*
354    *  jsMath.Autoload.Run() is now longer needed
355    */
356   Run: function (data) {},
357
358   /*
359    *  Look to see if there are SPAN or DIV elements of class "math".
360    */
361   areMathElements: function (obj) {
362     if (!obj) {obj = document}
363     if (typeof(obj) == 'string') {obj = document.getElementById(obj)}
364     if (!obj.getElementsByTagName) {return false}
365     var math = obj.getElementsByTagName('div');
366     for (var k = 0; k < math.length; k++) 
367       {if (math[k].className.match(/(^| )math( |$)/)) {return true}}
368     math = obj.getElementsByTagName('span');
369     for (var k = 0; k < math.length; k++) 
370       {if (math[k].className.match(/(^| )math( |$)/)) {return true}}
371     return false;
372   },
373
374   /*
375    *  When math tags are found, load the jsMath.js file,
376    *  and afterward, load any auxiliary files or fonts,
377    *  and then do any pending commands.
378    */
379   LoadJsMath: function () {
380     if (this.loading) return;
381     if (jsMath.loaded) {this.afterLoad(); return}
382     if (this.root) {
383       this.loading = 1;
384       this.setMessage('Loading jsMath...');
385       this.Script.AfterLoad = this.afterLoad;
386       this.Script.Load('jsMath.js');
387     } else {
388       alert("Can't determine URL for jsMath.js");
389     }
390   },
391   afterLoad: function () {
392     jsMath.Autoload.loading = 0;
393     //
394     //  Handle MSIE bug where jsMath.window both is and is not the actual window
395     //
396     if (jsMath.tex2math.window) {jsMath.tex2math.window.jsMath = jsMath}
397     if (jsMath.browser == 'MSIE') {window.onscroll = jsMath.window.onscroll};
398     var fonts = jsMath.Autoload.loadFonts;
399     if (fonts) {
400       if (typeof(fonts) != 'object') {fonts = [fonts]}
401       for (var i = 0; i < fonts.length; i++) {jsMath.Font.Load(fonts[i])}
402     }
403     var files = jsMath.Autoload.loadFiles;
404     if (files) {
405       if (typeof(files) != 'object') {files = [files]}
406       for (var i = 0; i < files.length; i++) {jsMath.Setup.Script(files[i])}
407     }
408     var macros = jsMath.Autoload.macros;
409     if (macros) {
410       for (var id in macros) {
411         if (typeof(macros[id]) == 'string') {
412           jsMath.Macro(id,macros[id]);
413         } else {
414           jsMath.Macro(id,macros[id][0],macros[id][1]);
415         }
416       }
417     }    
418     jsMath.Synchronize(function () {jsMath.Autoload.Script.RunStack()});
419     jsMath.Autoload.setMessage();
420   },
421
422   /*
423    *  Display a message in a small box at the bottom of the screen
424    */
425   setMessage: function (message) {
426     if (message) {
427       this.div = document.createElement('div');
428       if (!document.body.hasChildNodes) {document.body.appendChild(this.div)}
429         else {document.body.insertBefore(this.div,document.body.firstChild)}
430       var style = {
431         position:'fixed', bottom:'1px', left:'2px',
432         backgroundColor:'#E6E6E6', border:'solid 1px #959595',
433         margin:'0px', padding:'1px 8px', zIndex:102,
434         color:'black', fontSize:'75%', width:'auto'
435       };
436       for (var id in style) {this.div.style[id] = style[id]}
437       this.div.id = "jsMath_message";
438       this.div.appendChild(jsMath.document.createTextNode(message));
439     } else if (this.div) {
440       this.div.firstChild.nodeValue = "";
441       this.div.style.visibility = 'hidden';
442     }
443   },
444
445   /*
446    *  Queue these so we can do them after jsMath has been loaded
447    */
448   stubs: {
449     Process: function (data) {jsMath.Autoload.Script.Push('Process',[data])},
450     ProcessBeforeShowing: function (data) {jsMath.Autoload.Script.Push('ProcessBeforeShowing',[data])},
451     ProcessElement: function (data) {jsMath.Autoload.Script.Push('ProcessElement',[data])},
452     ConvertTeX: function (data) {jsMath.Autoload.Script.Push('ConvertTeX',[data])},
453     ConvertTeX2: function (data) {jsMath.Autoload.Script.Push('ConvertTeX2',[data])},
454     ConvertLaTeX: function (data) {jsMath.Autoload.Script.Push('ConvertLaTeX',[data])},
455     ConvertCustom: function (data) {jsMath.Autoload.Script.Push('ConvertCustom',[data])},
456     CustomSearch: function (d1,d2,d3,d4) {jsMath.Autoload.Script.Push('CustomSearch',[d1,d2,d3,d4])},
457     Synchronize: function (data) {jsMath.Autoload.Script.Push('Synchronize',[data])},
458     Macro: function (cs,def,params) {jsMath.Autoload.Script.Push('Macro',[cs,def,params])}
459   },
460
461   InitStubs: function () {jsMath.Add(jsMath,jsMath.Autoload.stubs)}
462   
463 });
464
465 /*
466  *  Initialize
467  */
468
469 if (jsMath.Autoload.findTeXstrings == null)   {jsMath.Autoload.findTeXstrings = 0}
470 if (jsMath.Autoload.findLaTeXstrings == null) {jsMath.Autoload.findLaTeXstrings = 0}
471
472 jsMath.Autoload.Script.Init();
473 jsMath.Autoload.InitStubs();
474 if (document.body && !jsMath.Autoload.delayCheck) {jsMath.Autoload.Check()}