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