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