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