1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*jslint nomen: true, bitwise: true, emptyblock: true, unparam: true */
 26 /*global window, XMLHttpRequest, require, console, DOMParser, document,
 27   process, __dirname, setTimeout, Packages, print,
 28   readFile, quit, Buffer, ArrayBuffer, Uint8Array,
 29   navigator, VBArray, alert, now, clearTimeout, webodf_version */
 30 
 31 /**
 32  * Three implementations of a runtime for browser, node.js and rhino.
 33  */
 34 
 35 /**
 36  * Abstraction of the runtime environment.
 37  * @class
 38  * @interface
 39  */
 40 function Runtime() {"use strict"; }
 41 
 42 /**
 43  * @param {!string} name
 44  * @return {*}
 45  */
 46 Runtime.prototype.getVariable = function (name) { "use strict"; };
 47 
 48 /**
 49  * @param {*} anything
 50  * @return {!string}
 51  */
 52 Runtime.prototype.toJson = function (anything) { "use strict"; };
 53 
 54 /**
 55  * @param {!string} jsonstr
 56  * @return {*}
 57  */
 58 Runtime.prototype.fromJson = function (jsonstr) { "use strict"; };
 59 
 60 /**
 61  * @param {!string} string
 62  * @param {!string} encoding
 63  * @return {!Uint8Array}
 64  */
 65 Runtime.prototype.byteArrayFromString = function (string, encoding) {"use strict"; };
 66 /**
 67  * @param {!Uint8Array} bytearray
 68  * @param {!string} encoding
 69  * @return {!string}
 70  */
 71 Runtime.prototype.byteArrayToString = function (bytearray, encoding) {"use strict"; };
 72 /**
 73  * Read part of a binary file.
 74  * @param {!string} path
 75  * @param {!number} offset
 76  * @param {!number} length
 77  * @param {!function(?string,?Uint8Array):undefined} callback
 78  * @return {undefined}
 79  */
 80 Runtime.prototype.read = function (path, offset, length, callback) {"use strict"; };
 81 /**
 82  * Read the contents of a file. Returns the result via a callback. If the
 83  * encoding is 'binary', the result is returned as a Uint8Array,
 84  * otherwise, it is returned as a string.
 85  * @param {!string} path
 86  * @param {!string} encoding text encoding or 'binary'
 87  * @param {!function(?string,?(string|Uint8Array)):undefined} callback
 88  * @return {undefined}
 89  */
 90 Runtime.prototype.readFile = function (path, encoding, callback) {"use strict"; };
 91 /**
 92  * Read a file completely, throw an exception if there is a problem.
 93  * @param {!string} path
 94  * @param {!string} encoding text encoding or 'binary'
 95  * @return {!string|!Uint8Array}
 96  */
 97 Runtime.prototype.readFileSync = function (path, encoding) {"use strict"; };
 98 /**
 99  * @param {!string} path
100  * @param {!function(?string,?Document):undefined} callback
101  * @return {undefined}
102  */
103 Runtime.prototype.loadXML = function (path, callback) {"use strict"; };
104 /**
105  * @param {!string} path
106  * @param {!Uint8Array} data
107  * @param {!function(?string):undefined} callback
108  * @return {undefined}
109  */
110 Runtime.prototype.writeFile = function (path, data, callback) {"use strict"; };
111 /**
112  * @param {!string} path
113  * @param {!function(?string):undefined} callback
114  * @return {undefined}
115  */
116 Runtime.prototype.deleteFile = function (path, callback) {"use strict"; };
117 /**
118  * @param {!string} msgOrCategory
119  * @param {!string=} msg
120  * @return {undefined}
121  */
122 Runtime.prototype.log = function (msgOrCategory, msg) {"use strict"; };
123 /**
124  * @param {!function():undefined} callback
125  * @param {!number} milliseconds
126  * @return {!number}
127  */
128 Runtime.prototype.setTimeout = function (callback, milliseconds) {"use strict"; };
129 /**
130  * @param {!number} timeoutID
131  * @return {undefined}
132  */
133 Runtime.prototype.clearTimeout = function (timeoutID) {"use strict"; };
134 /**
135  * @return {!Array.<string>}
136  */
137 Runtime.prototype.libraryPaths = function () {"use strict"; };
138 /**
139  * @return {!string}
140  */
141 Runtime.prototype.currentDirectory = function () {"use strict"; };
142 /**
143  * @param {!string} dir
144  * @return {undefined}
145  */
146 Runtime.prototype.setCurrentDirectory = function (dir) {"use strict"; };
147 /**
148  * @return {string}
149  */
150 Runtime.prototype.type = function () {"use strict"; };
151 /**
152  * @return {?DOMImplementation}
153  */
154 Runtime.prototype.getDOMImplementation = function () {"use strict"; };
155 /**
156  * @param {!string} xml
157  * @return {?Document}
158  */
159 Runtime.prototype.parseXML = function (xml) {"use strict"; };
160 /**
161  * @param {!number} exitCode
162  */
163 Runtime.prototype.exit = function (exitCode) {"use strict"; };
164 /**
165  * @return {?Window}
166  */
167 Runtime.prototype.getWindow = function () {"use strict"; };
168 /**
169  * @param {!function():undefined} callback
170  * @return {!number}
171  */
172 Runtime.prototype.requestAnimationFrame = function (callback) {"use strict"; };
173 /**
174  * @param {!number} requestId
175  * @return {undefined}
176  */
177 Runtime.prototype.cancelAnimationFrame = function (requestId) {"use strict"; };
178 /**
179  * @param {!boolean} condition
180  * @param {!string} message
181  * @return {undefined}
182  */
183 Runtime.prototype.assert = function (condition, message) { "use strict"; };
184 /*jslint emptyblock: false, unparam: false */
185 
186 /** @define {boolean} */
187 var IS_COMPILED_CODE = false;
188 
189 /**
190  * @this {Runtime}
191  * @param {!Uint8Array} bytearray
192  * @param {!string} encoding
193  * @return {!string}
194  */
195 Runtime.byteArrayToString = function (bytearray, encoding) {
196     "use strict";
197     /**
198      * @param {!Uint8Array} bytearray
199      * @return {!string}
200      */
201     function byteArrayToString(bytearray) {
202         var s = "", i, l = bytearray.length;
203         for (i = 0; i < l; i += 1) {
204             s += String.fromCharCode(bytearray[i] & 0xff);
205         }
206         return s;
207     }
208     /**
209      * @param {!Uint8Array} bytearray
210      * @return {!string}
211      */
212     function utf8ByteArrayToString(bytearray) {
213         var s = "", i, l = bytearray.length,
214             chars = [],
215             c0, c1, c2, c3, codepoint;
216 
217         for (i = 0; i < l; i += 1) {
218             c0 = /**@type{!number}*/(bytearray[i]);
219             if (c0 < 0x80) {
220                 chars.push(c0);
221             } else {
222                 i += 1;
223                 c1 = /**@type{!number}*/(bytearray[i]);
224                 if (c0 >= 0xc2 && c0 < 0xe0) {
225                     chars.push(((c0 & 0x1f) << 6) | (c1 & 0x3f));
226                 } else {
227                     i += 1;
228                     c2 = /**@type{!number}*/(bytearray[i]);
229                     if (c0 >= 0xe0 && c0 < 0xf0) {
230                         chars.push(((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f));
231                     } else {
232                         i += 1;
233                         c3 = /**@type{!number}*/(bytearray[i]);
234                         if (c0 >= 0xf0 && c0 < 0xf5) {
235                             codepoint = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f);
236                             codepoint -= 0x10000;
237                             chars.push((codepoint >> 10) + 0xd800, (codepoint & 0x3ff) + 0xdc00);
238                         }
239                     }
240                 }
241             }
242             if (chars.length >= 1000) {
243                 // more than 2 chars can be added in the above logic, so the length might exceed 1000
244 
245                 // Char-to-string conversion is done using apply as it provides a roughly %30 improvement vs.
246                 // converting the characters 1-by-1, and improves memory usage significantly as well.
247 
248                 // However, the apply function has an upper limit on the size of the arguments array. If it is exceeded,
249                 // most browsers with throw a RangeError. Avoid this problem by converting no more than 1000(ish)
250                 // characters per call.
251                 s += String.fromCharCode.apply(null, chars);
252                 chars.length = 0;
253             }
254         }
255         // Based on the above chars.length check, there is guaranteed to be less than 1000 chars left in the array
256         return s + String.fromCharCode.apply(null, chars);
257     }
258     var result;
259     if (encoding === "utf8") {
260         result = utf8ByteArrayToString(bytearray);
261     } else {
262         if (encoding !== "binary") {
263             this.log("Unsupported encoding: " + encoding);
264         }
265         result = byteArrayToString(bytearray);
266     }
267     return result;
268 };
269 
270 /**
271  * @param {!string} name
272  * @return {*}
273  */
274 Runtime.getVariable = function (name) {
275     "use strict";
276     /*jslint evil: true*/
277     try {
278         return eval(name);
279     } catch (e) {
280         return undefined;
281     }
282     /*jslint evil: false*/
283 };
284 
285 /**
286  * @param {*} anything
287  * @return {!string}
288  */
289 Runtime.toJson = function (anything) {
290     "use strict";
291     return JSON.stringify(anything);
292 };
293 
294 /**
295  * @param {!string} jsonstr
296  * @return {*}
297  */
298 Runtime.fromJson = function (jsonstr) {
299     "use strict";
300     return JSON.parse(jsonstr);
301 };
302 
303 /**
304  * @param {!Function} f
305  * @return {?string}
306  */
307 Runtime.getFunctionName = function getFunctionName(f) {
308     "use strict";
309     var m;
310     if (f.name === undefined) {
311         m = new RegExp("function\\s+(\\w+)").exec(f);
312         return m && m[1];
313     }
314     return f.name;
315 };
316 
317 /**
318  * @this {Runtime}
319  * @param {!boolean} condition
320  * @param {!string} message
321  * @return {undefined}
322  */
323 Runtime.assert = function (condition, message) {
324     "use strict";
325     if (!condition) {
326         this.log("alert", "ASSERTION FAILED:\n" + message);
327         throw new Error(message); // interrupt execution and provide a backtrace
328     }
329 };
330 
331 /**
332  * @class
333  * @constructor
334  * @augments Runtime
335  * @implements {Runtime}
336  * @param {Element} logoutput
337  */
338 function BrowserRuntime(logoutput) {
339     "use strict";
340     var self = this;
341 
342     /**
343      * Return the number of bytes a string would take up when encoded as utf-8.
344      * @param {string} string
345      * @return {number}
346      */
347     function getUtf8LengthForString(string) {
348         var l = string.length, i, n, j = 0;
349         for (i = 0; i < l; i += 1) {
350             n = string.charCodeAt(i);
351             j += 1 + (n > 0x80) + (n > 0x800);
352             if (n > 0xd700 && n < 0xe000) { // first of a surrogate pair
353                 j += 1;
354                 i += 1; // skip second half of in surrogate pair
355             }
356         }
357         return j;
358     }
359 
360     /**
361      * Convert UCS-2 string to UTF-8 array.
362      * @param {string} string
363      * @param {number} length the length of the resulting array
364      * @param {boolean} addBOM whether or not to start with a BOM
365      * @return {!Uint8Array}
366      */
367     function utf8ByteArrayFromString(string, length, addBOM) {
368         var l = string.length, bytearray, i, n,
369             j;
370         // allocate a buffer and convert to a utf8 array
371         bytearray = new Uint8Array(new ArrayBuffer(length));
372         if (addBOM) {
373             bytearray[0] = 0xef;
374             bytearray[1] = 0xbb;
375             bytearray[2] = 0xbf;
376             j = 3;
377         } else {
378             j = 0;
379         }
380         for (i = 0; i < l; i += 1) {
381             n = string.charCodeAt(i);
382             if (n < 0x80) {
383                 bytearray[j] = n;
384                 j += 1;
385             } else if (n < 0x800) {
386                 bytearray[j] = 0xc0 | (n >>>  6);
387                 bytearray[j + 1] = 0x80 | (n & 0x3f);
388                 j += 2;
389             } else if (n <= 0xd700 || n >= 0xe000) {
390                 bytearray[j] = 0xe0 | ((n >>> 12) & 0x0f);
391                 bytearray[j + 1] = 0x80 | ((n >>>  6) & 0x3f);
392                 bytearray[j + 2] = 0x80 |  (n         & 0x3f);
393                 j += 3;
394             } else { // surrogate pair
395                 i += 1;
396                 n = (((n - 0xd800) << 10) | (string.charCodeAt(i) - 0xdc00))
397                     + 0x10000;
398                 bytearray[j] = 0xf0 | (n >>> 18 & 0x07);
399                 bytearray[j + 1] = 0x80 | (n >>> 12 & 0x3f);
400                 bytearray[j + 2] = 0x80 | (n >>> 6 & 0x3f);
401                 bytearray[j + 3] = 0x80 | (n & 0x3f);
402                 j += 4;
403             }
404         }
405         return bytearray;
406     }
407     /**
408      * Convert UCS-2 string to UTF-8 array.
409      * wishLength is the desired length, if it is 3 bytes longer than
410      * forsee by the string data, a BOM is prepended.
411      * @param {string} string
412      * @param {(number|string)=} wishLength
413      * @return {!Uint8Array|undefined}
414      */
415     function utf8ByteArrayFromXHRString(string, wishLength) {
416         var addBOM = false,
417             length = getUtf8LengthForString(string);
418         if (typeof wishLength === "number") {
419             if (wishLength !== length && wishLength !== length + 3) {
420                 // the desired length does not match the content of the string
421                 return undefined;
422             }
423             addBOM = length + 3 === wishLength;
424             length = wishLength;
425         }
426         return utf8ByteArrayFromString(string, length, addBOM);
427     }
428     /**
429      * @param {!string} string
430      * @return {!Uint8Array}
431      */
432     function byteArrayFromString(string) {
433         // ignore encoding for now
434         var l = string.length,
435             a = new Uint8Array(new ArrayBuffer(l)),
436             i;
437         for (i = 0; i < l; i += 1) {
438             a[i] = string.charCodeAt(i) & 0xff;
439         }
440         return a;
441     }
442     /**
443      * @param {!string} string
444      * @param {!string} encoding
445      * @return {!Uint8Array}
446      */
447     this.byteArrayFromString = function (string, encoding) {
448         var result;
449         if (encoding === "utf8") {
450             result = utf8ByteArrayFromString(string,
451                 getUtf8LengthForString(string), false);
452         } else {
453             if (encoding !== "binary") {
454                 self.log("unknown encoding: " + encoding);
455             }
456             result = byteArrayFromString(string);
457         }
458         return result;
459     };
460     this.byteArrayToString = Runtime.byteArrayToString;
461 
462     /**
463     * @param {!string} name
464     * @return {*}
465     */
466     this.getVariable = Runtime.getVariable;
467 
468 
469     /**
470     * @param {!string} jsonstr
471     * @return {*}
472     */
473     this.fromJson = Runtime.fromJson;
474     /**
475     * @param {*} anything
476     * @return {!string}
477     */
478     this.toJson = Runtime.toJson;
479 
480     /**
481      * @param {!string} msgOrCategory
482      * @param {string=} msg
483      * @return {undefined}
484      */
485     function log(msgOrCategory, msg) {
486         var node, doc, category;
487         if (msg !== undefined) {
488             category = msgOrCategory;
489         } else {
490             msg = msgOrCategory;
491         }
492         if (logoutput) {
493             doc = logoutput.ownerDocument;
494             if (category) {
495                 node = doc.createElement("span");
496                 node.className = category;
497                 node.appendChild(doc.createTextNode(category));
498                 logoutput.appendChild(node);
499                 logoutput.appendChild(doc.createTextNode(" "));
500             }
501             node = doc.createElement("span");
502             if (msg.length > 0 && msg[0] === "<") {
503                 node.innerHTML = msg;
504             } else {
505                 node.appendChild(doc.createTextNode(msg));
506             }
507             logoutput.appendChild(node);
508             logoutput.appendChild(doc.createElement("br"));
509         } else if (console) {
510             console.log(msg);
511         }
512         if (self.enableAlerts && category === "alert") {
513             alert(msg);
514         }
515     }
516 
517     /**
518      * @param {!Array.<!number>} buffer
519      * @return {!Uint8Array}
520      */
521     function arrayToUint8Array(buffer) {
522         var l = buffer.length, i,
523             a = new Uint8Array(new ArrayBuffer(l));
524         for (i = 0; i < l; i += 1) {
525             a[i] = buffer[i];
526         }
527         return a;
528     }
529     /**
530      * Convert the text received by XHR to a byte array.
531      * An XHR request can send a text as a response even though binary content
532      * was requested. This text string should be converted to a byte array.
533      * If the length of the text is equal to the reported length of the content
534      * then each character becomes one byte.
535      * If the length is different, which can happen on WebKit and Blink
536      * browsers, the string should be converted while taking into account the
537      * encoding. Currently, only utf8 is supported for this procedure.
538      * @param {!XMLHttpRequest} xhr
539      * @return {!Uint8Array}
540      */
541     function stringToBinaryWorkaround(xhr) {
542         var cl, data;
543         cl = xhr.getResponseHeader("Content-Length");
544         if (cl) {
545             cl = parseInt(cl, 10);
546         }
547         // If Content-Length was found and is a valid number that is not equal
548         // to the length of the string, the byte array should be reconstructed
549         // from the encoding.
550         if (cl && cl !== xhr.responseText.length) {
551             // The text is not simple ascii, so we assume it is utf8 and try to
552             // reconstruct the text from that.
553             data = utf8ByteArrayFromXHRString(xhr.responseText, cl);
554         }
555         if (data === undefined) {
556             data = byteArrayFromString(xhr.responseText);
557         }
558         return data;
559     }
560     /**
561      * @param {!string} path
562      * @param {!string} encoding
563      * @param {!XMLHttpRequest} xhr
564      * @return {!{err:?string,data:(?string|?Uint8Array)}}
565      */
566     function handleXHRResult(path, encoding, xhr) {
567         var r, d, a,
568             /**@type{!Uint8Array|!string}*/
569             data;
570         if (xhr.status === 0 && !xhr.responseText) {
571             // for local files there is no difference between missing
572             // and empty files, so empty files are considered as errors
573             r = {err: "File " + path + " is empty.", data: null};
574         } else if (xhr.status === 200 || xhr.status === 0) {
575             // report file
576             if (xhr.response && typeof xhr.response !== "string") {
577                 // w3c compliant way http://www.w3.org/TR/XMLHttpRequest2/#the-response-attribute
578                 if (encoding === "binary") {
579                     d = /**@type{!ArrayBuffer}*/(xhr.response);
580                     data = new Uint8Array(d);
581                 } else {
582                     data = String(xhr.response);
583                 }
584             } else if (encoding === "binary") {
585                 if (xhr.responseBody !== null
586                         && String(typeof VBArray) !== "undefined") {
587                     // fallback for IE <= 10
588                     a = (new VBArray(xhr.responseBody)).toArray();
589                     data = arrayToUint8Array(a);
590                 } else {
591                     data = stringToBinaryWorkaround(xhr);
592                 }
593             } else {
594                 // if we just want text, it's simple
595                 data = xhr.responseText;
596             }
597             r = {err: null, data: data};
598         } else {
599             // report error
600             r = {err: xhr.responseText || xhr.statusText, data: null};
601         }
602         return r;
603     }
604     /**
605      * @param {!string} path
606      * @param {!string} encoding
607      * @param {!boolean} async
608      * @return {!XMLHttpRequest}
609      */
610     function createXHR(path, encoding, async) {
611         var xhr = new XMLHttpRequest();
612         xhr.open('GET', path, async);
613         if (xhr.overrideMimeType) {
614             if (encoding !== "binary") {
615                 xhr.overrideMimeType("text/plain; charset=" + encoding);
616             } else {
617                 xhr.overrideMimeType("text/plain; charset=x-user-defined");
618             }
619         }
620         return xhr;
621     }
622     /**
623      * Read the contents of a file. Returns the result via a callback. If the
624      * encoding is 'binary', the result is returned as a Uint8Array,
625      * otherwise, it is returned as a string.
626      * @param {!string} path
627      * @param {!string} encoding text encoding or 'binary'
628      * @param {!function(?string,?(string|Uint8Array)):undefined} callback
629      * @return {undefined}
630      */
631     function readFile(path, encoding, callback) {
632         var xhr = createXHR(path, encoding, true);
633         function handleResult() {
634             var r;
635             if (xhr.readyState === 4) {
636                 r = handleXHRResult(path, encoding, xhr);
637                 callback(r.err, r.data);
638             }
639         }
640         xhr.onreadystatechange = handleResult;
641         try {
642             xhr.send(null);
643         } catch (/**@type{!Error}*/e) {
644             callback(e.message, null);
645         }
646     }
647     /**
648      * @param {!string} path
649      * @param {!number} offset
650      * @param {!number} length
651      * @param {!function(?string,?Uint8Array):undefined} callback
652      * @return {undefined}
653      */
654     function read(path, offset, length, callback) {
655         readFile(path, "binary", function (err, result) {
656             var r = null;
657             if (result) {
658                 if (typeof result === "string") {
659                     throw "This should not happen.";
660                 }
661                 r = /**@type{!Uint8Array}*/(result.subarray(offset,
662                                                             offset + length));
663             }
664             callback(err, r);
665         });
666     }
667     /**
668      * @param {!string} path
669      * @param {!string} encoding text encoding or 'binary'
670      * @return {!string|!Uint8Array}
671      */
672     function readFileSync(path, encoding) {
673         var xhr = createXHR(path, encoding, false),
674             r;
675         try {
676             xhr.send(null);
677             r = handleXHRResult(path, encoding, xhr);
678             if (r.err) {
679                 throw r.err;
680             }
681             if (r.data === null) {
682                 throw "No data read from " + path + ".";
683             }
684         } catch (/**@type{!Error}*/e) {
685             throw e;
686         }
687         return r.data;
688     }
689     /**
690      * @param {!string} path
691      * @param {!Uint8Array} data
692      * @param {!function(?string):undefined} callback
693      * @return {undefined}
694      */
695     function writeFile(path, data, callback) {
696         var xhr = new XMLHttpRequest(),
697             /**@type{!string|!ArrayBuffer}*/
698             d;
699         function handleResult() {
700             if (xhr.readyState === 4) {
701                 if (xhr.status === 0 && !xhr.responseText) {
702                     // for local files there is no difference between missing
703                     // and empty files, so empty files are considered as errors
704                     callback("File " + path + " is empty.");
705                 } else if ((xhr.status >= 200 && xhr.status < 300) ||
706                            xhr.status === 0) {
707                     // report success
708                     callback(null);
709                 } else {
710                     // report error
711                     callback("Status " + String(xhr.status) + ": " +
712                             xhr.responseText || xhr.statusText);
713                 }
714             }
715         }
716         xhr.open('PUT', path, true);
717         xhr.onreadystatechange = handleResult;
718         // ArrayBufferView will have an ArrayBuffer property, in WebKit, XHR
719         // can send() an ArrayBuffer, In Firefox, one must use sendAsBinary with
720         // a string
721         if (data.buffer && !xhr.sendAsBinary) {
722             d = data.buffer; // webkit supports sending an ArrayBuffer
723         } else {
724             // encode into a string, this works in FireFox >= 3
725             d = self.byteArrayToString(data, "binary");
726         }
727         try {
728             if (xhr.sendAsBinary) {
729                 xhr.sendAsBinary(d);
730             } else {
731                 xhr.send(d);
732             }
733         } catch (/**@type{!Error}*/e) {
734             self.log("HUH? " + e + " " + data);
735             callback(e.message);
736         }
737     }
738     /**
739      * @param {!string} path
740      * @param {!function(?string):undefined} callback
741      * @return {undefined}
742      */
743     function deleteFile(path, callback) {
744         var xhr = new XMLHttpRequest();
745         xhr.open('DELETE', path, true);
746         xhr.onreadystatechange = function () {
747             if (xhr.readyState === 4) {
748                 if (xhr.status < 200 && xhr.status >= 300) {
749                     callback(xhr.responseText);
750                 } else {
751                     callback(null);
752                 }
753             }
754         };
755         xhr.send(null);
756     }
757     /**
758      * @param {!string} path
759      * @param {!function(?string,?Document):undefined} callback
760      * @return {undefined}
761      */
762     function loadXML(path, callback) {
763         var xhr = new XMLHttpRequest();
764         function handleResult() {
765             if (xhr.readyState === 4) {
766                 if (xhr.status === 0 && !xhr.responseText) {
767                     callback("File " + path + " is empty.", null);
768                 } else if (xhr.status === 200 || xhr.status === 0) {
769                     // report file
770                     callback(null, xhr.responseXML);
771                 } else {
772                     // report error
773                     callback(xhr.responseText, null);
774                 }
775             }
776         }
777         xhr.open("GET", path, true);
778         if (xhr.overrideMimeType) {
779             xhr.overrideMimeType("text/xml");
780         }
781         xhr.onreadystatechange = handleResult;
782         try {
783             xhr.send(null);
784         } catch (/**@type{!Error}*/e) {
785             callback(e.message, null);
786         }
787     }
788     this.readFile = readFile;
789     this.read = read;
790     this.readFileSync = readFileSync;
791     this.writeFile = writeFile;
792     this.deleteFile = deleteFile;
793     this.loadXML = loadXML;
794     this.log = log;
795     this.enableAlerts = true;
796     this.assert = Runtime.assert;
797     /**
798      * @param {!function():undefined} f
799      * @param {!number} msec
800      * @return {!number}
801      */
802     this.setTimeout = function (f, msec) {
803         return setTimeout(function () {
804             f();
805         }, msec);
806     };
807     /**
808      * @param {!number} timeoutID
809      * @return {undefined}
810      */
811     this.clearTimeout = function (timeoutID) {
812         clearTimeout(timeoutID);
813     };
814     /**
815      * @return {!Array.<!string>}
816      */
817     this.libraryPaths = function () {
818         return ["lib"]; // TODO: find a good solution
819                                        // probably let html app specify it
820     };
821     /*jslint emptyblock: true*/
822     this.setCurrentDirectory = function () {
823     };
824     /*jslint emptyblock: false*/
825     /**
826      * @return {!string}
827      */
828     this.currentDirectory = function () {
829         return "";
830     };
831     this.type = function () {
832         return "BrowserRuntime";
833     };
834     this.getDOMImplementation = function () {
835         return window.document.implementation;
836     };
837     /**
838      * @param {!string} xml
839      * @return {?Document}
840      */
841     this.parseXML = function (xml) {
842         var parser = new DOMParser();
843         return parser.parseFromString(xml, "text/xml");
844     };
845     /**
846      * @param {!number} exitCode
847      */
848     this.exit = function (exitCode) {
849         log("Calling exit with code " + String(exitCode) +
850                 ", but exit() is not implemented.");
851     };
852     /**
853      * @return {!Window}
854      */
855     this.getWindow = function () {
856         return window;
857     };
858     /**
859      * @param {!function():undefined} callback
860      * @return {!number}
861      */
862     this.requestAnimationFrame = function (callback) {
863         var rAF = window.requestAnimationFrame
864             || window.webkitRequestAnimationFrame
865             || window.mozRequestAnimationFrame
866             || window.msRequestAnimationFrame,
867             requestId = 0;
868 
869         if (rAF) {
870             // This code is to satisfy Closure, which expects
871             // that the `this` of rAF should be window.
872             rAF.bind(window);
873             requestId = /**@type{function(!function():undefined):!number}*/(rAF)(callback);
874         } else {
875             return setTimeout(callback, 15);
876         }
877 
878         return requestId;
879     };
880     /**
881      * @param {!number} requestId
882      * @return {undefined}
883      */
884     this.cancelAnimationFrame = function (requestId) {
885         var cAF = window.cancelAnimationFrame
886             || window.webkitCancelAnimationFrame
887             || window.mozCancelAnimationFrame
888             || window.msCancelAnimationFrame;
889 
890         if (cAF) {
891             cAF.bind(window);
892             /**@type{function(!number)}*/(cAF)(requestId);
893         } else {
894             clearTimeout(requestId);
895         }
896     };
897 }
898 
899 /**
900  * @constructor
901  * @implements {Runtime}
902  */
903 function NodeJSRuntime() {
904     "use strict";
905     var self = this,
906         fs = require('fs'),
907         pathmod = require('path'),
908         /**@type{!string}*/
909         currentDirectory = "",
910         /**@type{!DOMParser}*/
911         parser,
912         domImplementation;
913 
914     /**
915      * @param {!Buffer} buffer
916      * @return {!Uint8Array}
917      */
918     function bufferToUint8Array(buffer) {
919         var l = buffer.length, i,
920             a = new Uint8Array(new ArrayBuffer(l));
921         for (i = 0; i < l; i += 1) {
922             a[i] = buffer[i];
923         }
924         return a;
925     }
926     /**
927      * @param {!string} string
928      * @param {!string} encoding
929      * @return {!Uint8Array}
930      */
931     this.byteArrayFromString = function (string, encoding) {
932         var buf = new Buffer(string, encoding), i, l = buf.length,
933             a = new Uint8Array(new ArrayBuffer(l));
934         for (i = 0; i < l; i += 1) {
935             a[i] = buf[i];
936         }
937         return a;
938     };
939 
940     this.byteArrayToString = Runtime.byteArrayToString;
941 
942     /**
943     * @param {!string} name
944     * @return {*}
945     */
946     this.getVariable = Runtime.getVariable;
947 
948     /**
949     * @param {!string} jsonstr
950     * @return {*}
951     */
952     this.fromJson = Runtime.fromJson;
953     /**
954     * @param {*} anything
955     * @return {!string}
956     */
957     this.toJson = Runtime.toJson;
958 
959     /**
960      * Read the contents of a file. Returns the result via a callback. If the
961      * encoding is 'binary', the result is returned as a Uint8Array,
962      * otherwise, it is returned as a string.
963      * @param {!string} path
964      * @param {!string} encoding text encoding or 'binary'
965      * @param {!function(?string,?(string|Uint8Array)):undefined} callback
966      * @return {undefined}
967      */
968     function readFile(path, encoding, callback) {
969         /**
970          * @param {?string} err
971          * @param {?Buffer|?string} data
972          * @return {undefined}
973          */
974         function convert(err, data) {
975             if (err) {
976                 return callback(err, null);
977             }
978             if (!data) {
979                 return callback("No data for " + path + ".", null);
980             }
981             var d;
982             if (typeof data === "string") {
983                 d = /**@type{!string}*/(data);
984                 return callback(err, d);
985             }
986             d = /**@type{!Buffer}*/(data);
987             callback(err, bufferToUint8Array(d));
988         }
989         path = pathmod.resolve(currentDirectory, path);
990         if (encoding !== "binary") {
991             fs.readFile(path, encoding, convert);
992         } else {
993             fs.readFile(path, null, convert);
994         }
995     }
996     this.readFile = readFile;
997     /**
998      * @param {!string} path
999      * @param {!function(?string,?Document):undefined} callback
1000      * @return {undefined}
1001      */
1002     function loadXML(path, callback) {
1003         readFile(path, "utf-8", function (err, data) {
1004             if (err) {
1005                 return callback(err, null);
1006             }
1007             if (!data) {
1008                 return callback("No data for " + path + ".", null);
1009             }
1010             var d = /**@type{!string}*/(data);
1011             callback(null, self.parseXML(d));
1012         });
1013     }
1014     this.loadXML = loadXML;
1015     /**
1016      * @param {!string} path
1017      * @param {!Uint8Array} data
1018      * @param {!function(?string):undefined} callback
1019      * @return {undefined}
1020      */
1021     this.writeFile = function (path, data, callback) {
1022         var buf = new Buffer(data);
1023         path = pathmod.resolve(currentDirectory, path);
1024         fs.writeFile(path, buf, "binary", function (err) {
1025             callback(err || null);
1026         });
1027     };
1028     /**
1029      * @param {!string} path
1030      * @param {!function(?string):undefined} callback
1031      * @return {undefined}
1032      */
1033     this.deleteFile = function (path, callback) {
1034         path = pathmod.resolve(currentDirectory, path);
1035         fs.unlink(path, callback);
1036     };
1037     /**
1038      * @param {!string} path
1039      * @param {!number} offset
1040      * @param {!number} length
1041      * @param {!function(?string,?Uint8Array):undefined} callback
1042      * @return {undefined}
1043      */
1044     this.read = function (path, offset, length, callback) {
1045         path = pathmod.resolve(currentDirectory, path);
1046         fs.open(path, "r+", 666, function (err, fd) {
1047             if (err) {
1048                 callback(err, null);
1049                 return;
1050             }
1051             var buffer = new Buffer(length);
1052             fs.read(fd, buffer, 0, length, offset, function (err) {
1053                 fs.close(fd);
1054                 callback(err, bufferToUint8Array(buffer));
1055             });
1056         });
1057     };
1058     /**
1059      * @param {!string} path
1060      * @param {!string} encoding text encoding or 'binary'
1061      * @return {!string|!Uint8Array}
1062      */
1063     this.readFileSync = function (path, encoding) {
1064         var s,
1065             enc = (encoding === "binary") ? null : encoding,
1066             r = fs.readFileSync(path, enc);
1067         if (r === null) {
1068             throw "File " + path + " could not be read.";
1069         }
1070         if (encoding === "binary") {
1071             s = /**@type{!Buffer}*/(r);
1072             s = bufferToUint8Array(s);
1073         } else {
1074             s = /**@type{!string}*/(r);
1075         }
1076         return s;
1077     };
1078     /**
1079      * @param {!string} msgOrCategory
1080      * @param {string=} msg
1081      * @return {undefined}
1082      */
1083     function log(msgOrCategory, msg) {
1084         var category;
1085         if (msg !== undefined) {
1086             category = msgOrCategory;
1087         } else {
1088             msg = msgOrCategory;
1089         }
1090         if (category === "alert") {
1091             process.stderr.write("\n!!!!! ALERT !!!!!" + '\n');
1092         }
1093         process.stderr.write(msg + '\n');
1094         if (category === "alert") {
1095             process.stderr.write("!!!!! ALERT !!!!!" + '\n');
1096         }
1097     }
1098     this.log = log;
1099 
1100     this.assert = Runtime.assert;
1101 
1102     /**
1103      * @param {!function():undefined} f
1104      * @param {!number} msec
1105      * @return {!number}
1106      */
1107     this.setTimeout = function (f, msec) {
1108         return setTimeout(function () {
1109             f();
1110         }, msec);
1111     };
1112     /**
1113      * @param {!number} timeoutID
1114      * @return {undefined}
1115      */
1116     this.clearTimeout = function (timeoutID) {
1117         clearTimeout(timeoutID);
1118     };
1119     /**
1120      * @return {!Array.<!string>}
1121      */
1122     this.libraryPaths = function () {
1123         return [__dirname];
1124     };
1125     /**
1126      * @param {!string} dir
1127      * @return {undefined}
1128      */
1129     this.setCurrentDirectory = function (dir) {
1130         currentDirectory = dir;
1131     };
1132     this.currentDirectory = function () {
1133         return currentDirectory;
1134     };
1135     this.type = function () {
1136         return "NodeJSRuntime";
1137     };
1138     this.getDOMImplementation = function () {
1139         return domImplementation;
1140     };
1141     /**
1142      * @param {!string} xml
1143      * @return {?Document}
1144      */
1145     this.parseXML = function (xml) {
1146         return parser.parseFromString(xml, "text/xml");
1147     };
1148     this.exit = process.exit;
1149     this.getWindow = function () {
1150         return null;
1151     };
1152     /**
1153      * @param {!function():undefined} callback
1154      * @return {!number}
1155      */
1156     this.requestAnimationFrame = function (callback) {
1157         return setTimeout(callback, 15);
1158     };
1159     /**
1160      * @param {!number} requestId
1161      * @return {undefined}
1162      */
1163     this.cancelAnimationFrame = function (requestId) {
1164         clearTimeout(requestId);
1165     };
1166     function init() {
1167         var /**@type{function(new:DOMParser)}*/
1168             DOMParser = require('xmldom').DOMParser;
1169         parser = new DOMParser();
1170         domImplementation = self.parseXML("<a/>").implementation;
1171     }
1172     init();
1173 }
1174 
1175 /**
1176  * @constructor
1177  * @implements {Runtime}
1178  */
1179 function RhinoRuntime() {
1180     "use strict";
1181     var self = this,
1182         Packages = {},
1183         dom = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(),
1184         /**@type{!Packages.javax.xml.parsers.DocumentBuilder}*/
1185         builder,
1186         entityresolver,
1187         /**@type{!string}*/
1188         currentDirectory = "";
1189     dom.setValidating(false);
1190     dom.setNamespaceAware(true);
1191     dom.setExpandEntityReferences(false);
1192     dom.setSchema(null);
1193     /*jslint unparam: true */
1194     entityresolver = Packages.org.xml.sax.EntityResolver({
1195         /**
1196          * @param {!string} publicId
1197          * @param {!string} systemId
1198          * @return {!Packages.org.xml.sax.InputSource}
1199          */
1200         resolveEntity: function (publicId, systemId) {
1201             var file;
1202             /**
1203              * @param {!string} path
1204              * @return {!Packages.org.xml.sax.InputSource}
1205              */
1206             function open(path) {
1207                 var reader = new Packages.java.io.FileReader(path),
1208                     source = new Packages.org.xml.sax.InputSource(reader);
1209                 return source;
1210             }
1211             file = systemId;
1212             //file = /[^\/]*$/.exec(systemId); // what should this do?
1213             return open(file);
1214         }
1215     });
1216     /*jslint unparam: false */
1217     //dom.setEntityResolver(entityresolver);
1218     builder = dom.newDocumentBuilder();
1219     builder.setEntityResolver(entityresolver);
1220 
1221     /*jslint unparam: true*/
1222     /**
1223      * @param {!string} string
1224      * @param {!string} encoding
1225      * @return {!Uint8Array}
1226      */
1227     this.byteArrayFromString = function (string, encoding) {
1228         // ignore encoding for now
1229         var i,
1230             l = string.length,
1231             a = new Uint8Array(new ArrayBuffer(l));
1232         for (i = 0; i < l; i += 1) {
1233             a[i] = string.charCodeAt(i) & 0xff;
1234         }
1235         return a;
1236     };
1237     /*jslint unparam: false*/
1238     this.byteArrayToString = Runtime.byteArrayToString;
1239 
1240     /**
1241     * @param {!string} name
1242     * @return {*}
1243     */
1244     this.getVariable = Runtime.getVariable;
1245 
1246     /**
1247     * @param {!string} jsonstr
1248     * @return {*}
1249     */
1250     this.fromJson = Runtime.fromJson;
1251     /**
1252     * @param {*} anything
1253     * @return {!string}
1254     */
1255     this.toJson = Runtime.toJson;
1256 
1257     /**
1258      * @param {!string} path
1259      * @param {!function(?string,?Document):undefined} callback
1260      * @return {undefined}
1261      */
1262     function loadXML(path, callback) {
1263         var file = new Packages.java.io.File(path),
1264             xmlDocument = null;
1265         try {
1266             xmlDocument = builder.parse(file);
1267         } catch (/**@type{!string}*/err) {
1268             print(err);
1269             return callback(err, null);
1270         }
1271         callback(null, xmlDocument);
1272     }
1273     /**
1274      * @param {!string} path
1275      * @param {!string} encoding text encoding or 'binary'
1276      * @param {!function(?string,?(string|Uint8Array)):undefined} callback
1277      * @return {undefined}
1278      */
1279     function runtimeReadFile(path, encoding, callback) {
1280         if (currentDirectory) {
1281             path = currentDirectory + "/" + path;
1282         }
1283         var file = new Packages.java.io.File(path),
1284             data,
1285             // read binary, seems hacky but works
1286             rhinoencoding = (encoding === "binary") ? "latin1" : encoding;
1287         if (!file.isFile()) {
1288             callback(path + " is not a file.", null);
1289         } else {
1290             data = readFile(path, rhinoencoding);
1291             if (data && encoding === "binary") {
1292                 data = self.byteArrayFromString(data, "binary");
1293             }
1294             callback(null, data);
1295         }
1296     }
1297     /**
1298      * @param {!string} path
1299      * @param {!string} encoding
1300      * @return {?string}
1301      */
1302     function runtimeReadFileSync(path, encoding) {
1303         var file = new Packages.java.io.File(path);
1304         if (!file.isFile()) {
1305             return null;
1306         }
1307         if (encoding === "binary") {
1308             encoding = "latin1"; // read binary, seems hacky but works
1309         }
1310         return readFile(path, encoding);
1311     }
1312     this.loadXML = loadXML;
1313     this.readFile = runtimeReadFile;
1314     /**
1315      * @param {!string} path
1316      * @param {!Uint8Array} data
1317      * @param {!function(?string):undefined} callback
1318      * @return {undefined}
1319      */
1320     this.writeFile = function (path, data, callback) {
1321         if (currentDirectory) {
1322             path = currentDirectory + "/" + path;
1323         }
1324         var out = new Packages.java.io.FileOutputStream(path),
1325             i,
1326             l = data.length;
1327         for (i = 0; i < l; i += 1) {
1328             out.write(data[i]);
1329         }
1330         out.close();
1331         callback(null);
1332     };
1333     /**
1334      * @param {!string} path
1335      * @param {!function(?string):undefined} callback
1336      * @return {undefined}
1337      */
1338     this.deleteFile = function (path, callback) {
1339         if (currentDirectory) {
1340             path = currentDirectory + "/" + path;
1341         }
1342         var file = new Packages.java.io.File(path),
1343             otherPath = path + Math.random(),
1344             other = new Packages.java.io.File(otherPath);
1345         // 'delete' cannot be used with closure compiler, so we use a workaround
1346         if (file.rename(other)) {
1347             other.deleteOnExit();
1348             callback(null);
1349         } else {
1350             callback("Could not delete " + path);
1351         }
1352     };
1353     /**
1354      * @param {!string} path
1355      * @param {!number} offset
1356      * @param {!number} length
1357      * @param {!function(?string,?Uint8Array):undefined} callback
1358      * @return {undefined}
1359      */
1360     this.read = function (path, offset, length, callback) {
1361         // TODO: adapt to read only a part instead of the whole file
1362         if (currentDirectory) {
1363             path = currentDirectory + "/" + path;
1364         }
1365         var data = runtimeReadFileSync(path, "binary");
1366         if (data) {
1367             callback(null, this.byteArrayFromString(
1368                 data.substring(offset, offset + length),
1369                 "binary"
1370             ));
1371         } else {
1372             callback("Cannot read " + path, null);
1373         }
1374     };
1375     /**
1376      * @param {!string} path
1377      * @param {!string} encoding text encoding or 'binary'
1378      * @return {!string}
1379      */
1380     this.readFileSync = function (path, encoding) {
1381         if (!encoding) {
1382             return "";
1383         }
1384         var s = readFile(path, encoding);
1385         if (s === null) {
1386             throw "File could not be read.";
1387         }
1388         return s;
1389     };
1390     /**
1391      * @param {!string} msgOrCategory
1392      * @param {string=} msg
1393      * @return {undefined}
1394      */
1395     function log(msgOrCategory, msg) {
1396         var category;
1397         if (msg !== undefined) {
1398             category = msgOrCategory;
1399         } else {
1400             msg = msgOrCategory;
1401         }
1402         if (category === "alert") {
1403             print("\n!!!!! ALERT !!!!!");
1404         }
1405         print(msg);
1406         if (category === "alert") {
1407             print("!!!!! ALERT !!!!!");
1408         }
1409     }
1410     this.log = log;
1411 
1412     this.assert = Runtime.assert;
1413 
1414     /**
1415      * @param {!function():undefined} f
1416      * @return {!number}
1417      */
1418     this.setTimeout = function (f) {
1419         f();
1420         return 0;
1421     };
1422     /*jslint emptyblock: true */
1423     /**
1424      * @return {undefined}
1425      */
1426     this.clearTimeout = function () {
1427     };
1428     /*jslint emptyblock: false */
1429     /**
1430      * @return {!Array.<!string>}
1431      */
1432     this.libraryPaths = function () {
1433         return ["lib"];
1434     };
1435     /**
1436      * @param {!string} dir
1437      */
1438     this.setCurrentDirectory = function (dir) {
1439         currentDirectory = dir;
1440     };
1441     this.currentDirectory = function () {
1442         return currentDirectory;
1443     };
1444     this.type = function () {
1445         return "RhinoRuntime";
1446     };
1447     this.getDOMImplementation = function () {
1448         return builder.getDOMImplementation();
1449     };
1450     /**
1451      * @param {!string} xml
1452      * @return {?Document}
1453      */
1454     this.parseXML = function (xml) {
1455         var reader = new Packages.java.io.StringReader(xml),
1456             source = new Packages.org.xml.sax.InputSource(reader);
1457         return builder.parse(source);
1458     };
1459     this.exit = quit;
1460     this.getWindow = function () {
1461         return null;
1462     };
1463     /**
1464      * @param {!function():undefined} callback
1465      * @return {!number}
1466      */
1467     this.requestAnimationFrame = function (callback) {
1468         callback();
1469         return 0;
1470     };
1471     /*jslint emptyblock: true */
1472     /**
1473      * @return {undefined}
1474      */
1475     this.cancelAnimationFrame = function () {
1476     };
1477     /*jslint emptyblock: false */
1478 }
1479 
1480 /**
1481  * @return {!Runtime}
1482  */
1483 Runtime.create = function create() {
1484     "use strict";
1485     var /**@type{!Runtime}*/
1486         result;
1487     if (String(typeof window) !== "undefined") {
1488         result = new BrowserRuntime(window.document.getElementById("logoutput"));
1489     } else if (String(typeof require) !== "undefined") {
1490         result = new NodeJSRuntime();
1491     } else {
1492         result = new RhinoRuntime();
1493     }
1494     return result;
1495 };
1496 
1497 /**
1498  * @const
1499  * @type {!Runtime}
1500  */
1501 var runtime = Runtime.create();
1502 
1503 /**
1504  * @namespace The core package.
1505  */
1506 var core = {};
1507 /**
1508  * @namespace The gui package.
1509  */
1510 var gui = {};
1511 /**
1512  * @namespace The xmldom package.
1513  */
1514 var xmldom = {};
1515 /**
1516  * @namespace The ODF package.
1517  */
1518 var odf = {};
1519 /**
1520  * @namespace The editing operations
1521  */
1522 var ops = {};
1523 
1524 /**
1525  * @namespace The webodf namespace
1526  */
1527 var webodf = {};
1528 
1529 (function () {
1530     "use strict";
1531     /**
1532      * @return {string}
1533      */
1534     function getWebODFVersion() {
1535         var version = (String(typeof webodf_version) !== "undefined"
1536             ? webodf_version
1537             : "From Source"
1538         );
1539         return version;
1540     }
1541     /**
1542      * @const
1543      * @type {!string}
1544      */
1545     webodf.Version = getWebODFVersion();
1546 }());
1547 
1548 /*jslint sloppy: true*/
1549 (function () {
1550     /**
1551      * @param {string} dir  Needs to be non-empty, use "." to denote same directory
1552      * @param {!Object.<string,!{dir:string, deps:!Array.<string>}>} dependencies
1553      * @param {!boolean=} expectFail  Set to true if it is not known if there is a manifest
1554      */
1555     function loadDependenciesFromManifest(dir, dependencies, expectFail) {
1556         "use strict";
1557         var path = dir + "/manifest.json",
1558             content,
1559             list,
1560             manifest,
1561             /**@type{string}*/
1562             m;
1563         runtime.log("Loading manifest: "+path);
1564         try {
1565             content = runtime.readFileSync(path, "utf-8");
1566         } catch (/**@type{string}*/e) {
1567             if (expectFail) {
1568                 runtime.log("No loadable manifest found.");
1569             } else {
1570                 console.log(String(e));
1571                 throw e;
1572             }
1573             return;
1574         }
1575         list = JSON.parse(/**@type{string}*/(content));
1576         manifest = /**@type{!Object.<!Array.<string>>}*/(list);
1577         for (m in manifest) {
1578             if (manifest.hasOwnProperty(m)) {
1579                 dependencies[m] = {dir: dir, deps: manifest[m]};
1580             }
1581         }
1582     }
1583     /**
1584      * @return {!Object.<string,!{dir:string, deps:!Array.<string>}>}
1585      */
1586     function loadDependenciesFromManifests() {
1587         "use strict";
1588         var /**@type{!Object.<string,!{dir:string, deps:!Array.<string>}>}*/
1589             dependencies = [],
1590             paths = runtime.libraryPaths(),
1591             i;
1592         // Convenience: try to load any possible manifest in the current directory
1593         // but only if it not also included in the library paths
1594         if (runtime.currentDirectory() && paths.indexOf(runtime.currentDirectory()) === -1) {
1595             // there is no need to have a manifest there, so allow loading to fail
1596             loadDependenciesFromManifest(runtime.currentDirectory(), dependencies, true);
1597         }
1598         for (i = 0; i < paths.length; i += 1) {
1599             loadDependenciesFromManifest(paths[i], dependencies);
1600         }
1601         return dependencies;
1602     }
1603     /**
1604      * @param {string} dir
1605      * @param {string} className
1606      * @return {string}
1607      */
1608     function getPath(dir, className) {
1609         "use strict";
1610         return dir + "/" + className.replace(".", "/") + ".js";
1611     }
1612     /**
1613      * Create a list of required classes from a list of desired classes.
1614      * A new list is created that lists all classes that still need to be loaded
1615      * to load the list of desired classes.
1616      * @param {!Array.<string>} classNames
1617      * @param {!Object.<string,!{dir:string, deps:!Array.<string>}>} dependencies
1618      * @param {function(string):boolean} isDefined
1619      * @return {!Array.<string>}
1620      */
1621     function getLoadList(classNames, dependencies, isDefined) {
1622         "use strict";
1623         var loadList = [],
1624             stack = {},
1625             /**@type{!Object.<string,boolean>}*/
1626             visited = {};
1627         /**
1628          * @param {string} n
1629          */
1630         function visit(n) {
1631             if (visited[n] || isDefined(n)) {
1632                 return;
1633             }
1634             if (stack[n]) {
1635                 throw "Circular dependency detected for " + n + ".";
1636             }
1637             stack[n] = true;
1638             if (!dependencies[n]) {
1639                 throw "Missing dependency information for class " + n + ".";
1640             }
1641             var d = dependencies[n], deps = d.deps, i, l = deps.length;
1642             for (i = 0; i < l; i += 1) {
1643                 visit(deps[i]);
1644             }
1645             stack[n] = false;
1646             visited[n] = true;
1647             loadList.push(getPath(d.dir, n));
1648         }
1649         classNames.forEach(visit);
1650         return loadList;
1651     }
1652     /**
1653      * @param {string} path
1654      * @param {string} content
1655      * @return {string}
1656      */
1657     function addContent(path, content) {
1658         "use strict";
1659         content += "\n//# sourceURL=" + path;
1660         return content;
1661     }
1662     /**
1663      * @param {!Array.<string>} paths
1664      */
1665     function loadFiles(paths) {
1666         // this function is not strict, so eval can assign to globals
1667         var i,
1668             content;
1669         for (i = 0; i < paths.length; i += 1) {
1670             content = runtime.readFileSync(paths[i], "utf-8");
1671             content = addContent(paths[i], /**@type{string}*/(content));
1672             /*jslint evil: true*/
1673             eval(content);
1674             /*jslint evil: false*/
1675         }
1676     }
1677     /**
1678      * Load scripts by adding <script/> elements to the DOM.
1679      * The new script tags are added after the <script/> tag for runtime.js.
1680      * The scripts are added with async = false so that they are executed in the
1681      * right order. The scripts are executed when control returns to the browser
1682      * from the current stack.
1683      * If a callback is provided, it is executed after the last script has run.
1684      * @param {!Array.<string>} paths array with one or more script paths
1685      * @param {!Function=} callback
1686      */
1687     function loadFilesInBrowser(paths, callback) {
1688         "use strict";
1689         var e = document.currentScript || document.documentElement.lastChild,
1690             df = document.createDocumentFragment(),
1691             script,
1692             i;
1693         for (i = 0; i < paths.length; i += 1) {
1694             script = document.createElement("script");
1695             script.type = "text/javascript";
1696             script.charset = "utf-8";
1697             script.async = false; // execute the scripts in order
1698             script.setAttribute("src", paths[i]);
1699             df.appendChild(script);
1700         }
1701         if (callback) {
1702             script.onload = callback;
1703         }
1704         e.parentNode.insertBefore(df, e);
1705     }
1706     var /**@type{!Object.<string,!{dir:string, deps:!Array.<string>}>}*/
1707         dependencies,
1708         packages = {
1709             core: core,
1710             gui: gui,
1711             xmldom: xmldom,
1712             odf: odf,
1713             ops: ops
1714         };
1715     /**
1716      * Check if a class has been defined.
1717      * For class "core.sub.Name", this checks if there is an entry
1718      * packages.core.sub.Name.
1719      * @param {string} classname
1720      * @return {boolean}
1721      */
1722     function isDefined(classname) {
1723         "use strict";
1724         var parts = classname.split("."), i,
1725             /**@type{Object}*/
1726             p = packages,
1727             l = parts.length;
1728         for (i = 0; i < l; i += 1) {
1729             if (!p.hasOwnProperty(parts[i])) {
1730                 return false;
1731             }
1732             p = /**@type{Object}*/(p[parts[i]]);
1733         }
1734         return true;
1735     }
1736     /**
1737      * @param {!Array.<string>} classnames
1738      * @param {function():undefined=} callback
1739      * @returns {undefined}
1740      */
1741     runtime.loadClasses = function (classnames, callback) {
1742         "use strict";
1743         if (IS_COMPILED_CODE || classnames.length === 0) {
1744             return callback && callback();
1745         }
1746         dependencies = dependencies || loadDependenciesFromManifests();
1747         classnames = getLoadList(classnames, dependencies, isDefined);
1748         if (classnames.length === 0) {
1749             return callback && callback();
1750         }
1751         if (runtime.type() === "BrowserRuntime" && callback) {
1752             loadFilesInBrowser(classnames, callback);
1753         } else {
1754             loadFiles(classnames);
1755             if (callback) {
1756                 callback();
1757             }
1758         }
1759     };
1760     /**
1761      * @param {string} classname
1762      * @param {function():undefined=} callback
1763      * @return {undefined}
1764      * @expose
1765      */
1766     runtime.loadClass = function (classname, callback) {
1767         "use strict";
1768         runtime.loadClasses([classname], callback);
1769     };
1770 }());
1771 /*jslint sloppy: false*/
1772 
1773 (function () {
1774     "use strict";
1775     /**
1776      * @param {!string} string
1777      * @return {!string}
1778      */
1779     var translator = function (string) {
1780         return string;
1781     };
1782     /*jslint emptyblock: false*/
1783 
1784     /**
1785      * Translator function. Takes the original string
1786      * and returns the translation if it exists, else
1787      * returns the original.
1788      * @param {!string} original
1789      * @return {!string}
1790      */
1791     function tr(original) {
1792         var result = translator(original);
1793         if (!result || (String(typeof result) !== "string")) {
1794             return original;
1795         }
1796         return result;
1797     }
1798 
1799     /**
1800      * Gets the custom translator function
1801      * @return {!function(!string):!string}
1802      */
1803     runtime.getTranslator = function () {
1804         return translator;
1805     };
1806     /**
1807      * Set an external translator function
1808      * @param {!function(!string):!string} translatorFunction
1809      * @return {undefined}
1810      */
1811     runtime.setTranslator = function (translatorFunction) {
1812         translator = translatorFunction;
1813     };
1814     /**
1815      * @param {string} original
1816      * @return {string}
1817      */
1818     runtime.tr = tr;
1819 }());
1820 
1821 /*jslint sloppy: true*/
1822 (function (args) {
1823     if (args) {
1824         args = Array.prototype.slice.call(/**@type{{length:number}}*/(args));
1825     } else {
1826         args = [];
1827     }
1828 
1829     /*jslint unvar: true, defined: true*/
1830     /**
1831      * @param {!Array.<!string>} argv
1832      */
1833     function run(argv) {
1834         if (!argv.length) {
1835             return;
1836         }
1837         var script = argv[0];
1838         runtime.readFile(script, "utf8", function (err, code) {
1839             var path = "",
1840                 pathEndIndex = script.lastIndexOf("/"),
1841                 codestring = /**@type{string}*/(code);
1842 
1843             if (pathEndIndex !== -1) {
1844                 path = script.substring(0, pathEndIndex);
1845             } else {
1846                 path = ".";
1847             }
1848             runtime.setCurrentDirectory(path);
1849             function inner_run() {
1850                 var script, path, args, argv, result; // hide variables
1851                 // execute script and make arguments available via argv
1852                 /*jslint evil: true*/
1853                 result = /**@type{!number}*/(eval(codestring));
1854                 /*jslint evil: false*/
1855                 if (result) {
1856                     runtime.exit(result);
1857                 }
1858                 return;
1859             }
1860             if (err) {
1861                 runtime.log(err);
1862                 runtime.exit(1);
1863             } else if (codestring === null) {
1864                 runtime.log("No code found for " + script);
1865                 runtime.exit(1);
1866             } else {
1867                 // run the script with arguments bound to arguments parameter
1868                 inner_run.apply(null, argv);
1869             }
1870         });
1871     }
1872     /*jslint unvar: false, defined: false*/
1873     // if rhino or node.js, run the scripts provided as arguments
1874     if (runtime.type() === "NodeJSRuntime") {
1875         run(process.argv.slice(2));
1876     } else if (runtime.type() === "RhinoRuntime") {
1877         run(args);
1878     } else {
1879         run(args.slice(1));
1880     }
1881 }(String(typeof arguments) !== "undefined" && arguments));
1882