1 /* 2 * ilib.js - define the ilib name space 3 * 4 * Copyright © 2012-2021, JEDLSoft 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 /** 21 * @namespace The global namespace that contains general ilib functions useful 22 * to all of ilib 23 * 24 * @version // !macro ilibVersion 25 */ 26 var ilib = ilib || {}; 27 28 /** @private */ 29 ilib._ver = function() { 30 return // !macro ilibVersion 31 ; 32 }; 33 34 /** 35 * Return the current version of ilib. 36 * 37 * @static 38 * @return {string} a version string for this instance of ilib 39 */ 40 ilib.getVersion = function () { 41 if (ilib._dyncode) { 42 try { 43 var pkg; 44 pkg = require("../package.json"); 45 return pkg.version; 46 } catch (e) { 47 // ignore 48 } 49 } 50 return ilib._ver() || "14.0"; 51 }; 52 53 /** 54 * Place where resources and such are eventually assigned. 55 */ 56 ilib.data = { 57 /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */ 58 norm: { 59 ccc: {}, 60 nfd: {}, 61 nfc: {}, 62 nfkd: {}, 63 nfkc: {} 64 }, 65 zoneinfo: { 66 "Etc/UTC":{"o":"0:0","f":"UTC"}, 67 "local":{"f":"local"} 68 }, 69 /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {}, 70 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 71 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 72 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 73 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 74 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 75 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 76 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 77 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 78 /** @type {null|Array.<string>} */ timezones: [], 79 cache: {} 80 }; 81 82 /* 83 if (typeof(window) !== 'undefined') { 84 window["ilib"] = ilib; 85 } 86 */ 87 88 // export ilib for use as a module in nodejs 89 if (typeof(module) !== 'undefined') { 90 module.exports = ilib; 91 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 92 } 93 94 /** 95 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 96 * internationalization aspects of software. Instead of translating the text of the software 97 * into a foreign language, as in the process of localization, the textual elements of an application 98 * are replaced with an altered version of the original language.These specific alterations make 99 * the original words appear readable, but include the most problematic characteristics of 100 * the world's languages: varying length of text or characters, language direction, and so on. 101 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 102 * 103 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 104 */ 105 ilib.setAsPseudoLocale = function (localename) { 106 if (localename) { 107 ilib.pseudoLocales.push(localename) 108 } 109 }; 110 111 /** 112 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 113 * @static 114 */ 115 ilib.clearPseudoLocales = function() { 116 ilib.pseudoLocales = [ 117 "zxx-XX", 118 "zxx-Cyrl-XX", 119 "zxx-Hans-XX", 120 "zxx-Hebr-XX" 121 ]; 122 }; 123 124 ilib.clearPseudoLocales(); 125 126 /** 127 * Return the name of the platform 128 * @private 129 * @static 130 * @return {string} string naming the platform 131 */ 132 ilib._getPlatform = function () { 133 if (!ilib._platform) { 134 try { 135 if (typeof(java.lang.Object) !== 'undefined') { 136 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 137 return ilib._platform; 138 } 139 } catch (e) {} 140 141 if (typeof(global) !== 'undefined' && global.process && 142 ((global.process.versions && global.process.versions.node && typeof(module) !== 'undefined') || 143 (typeof(global.process.iotjs) !== "undefined"))) { 144 ilib._platform = "nodejs"; 145 } else if (typeof(Qt) !== 'undefined') { 146 ilib._platform = "qt"; 147 ilib._cacheMerged = true; // qt is too slow, so we need to cache the already-merged locale data 148 } else if (typeof(PalmSystem) !== 'undefined') { 149 ilib._platform = (typeof(window) !== 'undefined') ? "webos-webapp" : "webos"; 150 } else if (typeof(window) !== 'undefined') { 151 ilib._platform = "browser"; 152 } else { 153 ilib._platform = "unknown"; 154 } 155 } 156 return ilib._platform; 157 }; 158 159 /** 160 * If this ilib is running in a browser, return the name of that browser. 161 * @private 162 * @static 163 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 164 * "safari", or "opera"), or undefined if this is not running in a browser or if 165 * the browser name could not be determined 166 */ 167 ilib._getBrowser = function () { 168 var browser = undefined; 169 if (ilib._getPlatform() === "browser") { 170 if (navigator && navigator.userAgent) { 171 if (navigator.userAgent.indexOf("Firefox") > -1) { 172 return "firefox"; 173 } 174 if (navigator.userAgent.search(/Opera|OPR/) > -1 ) { 175 return "opera"; 176 } 177 if (navigator.userAgent.indexOf("Chrome") > -1) { 178 return "chrome"; 179 } 180 if (navigator.userAgent.indexOf(" .NET") > -1) { 181 return "ie"; 182 } 183 if (navigator.userAgent.indexOf("Safari") > -1) { 184 // chrome also has the string Safari in its userAgent, but the chrome case is 185 // already taken care of above 186 return "safari"; 187 } 188 if (navigator.userAgent.indexOf("Edge") > -1) { 189 return "Edge"; 190 } 191 if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) { 192 // Due to constraints of the iOS platform, 193 // all browser must be built on top of the WebKit rendering engine 194 return "iOS"; 195 } 196 } 197 } 198 return "unknown"; 199 }; 200 201 /** 202 * Return the value of the top object in the system. This could be global 203 * for node, or window for browsers, etc. 204 * @private 205 * @static 206 * @return {Object|undefined} the top variable, or undefined if there is none on this 207 * platform 208 */ 209 ilib._top = function() { 210 if (typeof(this.top) === 'undefined') { 211 this.top = null; 212 switch (ilib._getPlatform()) { 213 case "rhino": 214 this.top = (function() { 215 return (typeof global === 'object') ? global : this; 216 })(); 217 break; 218 case "nodejs": 219 case "trireme": 220 this.top = typeof(global) !== 'undefined' ? global : this; 221 //console.log("ilib._top: top is " + (typeof(global) !== 'undefined' ? "global" : "this")); 222 break; 223 default: 224 this.top = window; 225 break; 226 } 227 } 228 229 return this.top || undefined; 230 }; 231 232 /** 233 * Return the value of a global variable given its name in a way that works 234 * correctly for the current platform. 235 * @private 236 * @static 237 * @param {string} name the name of the variable to return 238 * @return {*} the global variable, or undefined if it does not exist 239 */ 240 ilib._global = function(name) { 241 var top = this._top(); 242 try { 243 return top[name]; 244 } catch (e) { 245 return undefined; 246 } 247 }; 248 249 /** 250 * Return true if the global variable is defined on this platform. 251 * @private 252 * @static 253 * @param {string} name the name of the variable to check 254 * @return {boolean} true if the global variable is defined on this platform, false otherwise 255 */ 256 ilib._isGlobal = function(name) { 257 return typeof(ilib._global(name)) !== 'undefined'; 258 }; 259 260 /** 261 * Clear the file load cache. This is mainly used by the unit tests, 262 * but could be used by regular callers if you want to free up memory 263 * for garbage collecting. 264 */ 265 ilib.clearCache = function() { 266 ilib.data.cache = {}; 267 }; 268 269 /** 270 * Sets the default locale for all of ilib. This locale will be used 271 * when no explicit locale is passed to any ilib class. If the default 272 * locale is not set, ilib will attempt to use the locale of the 273 * environment it is running in, if it can find that. If not, it will 274 * default to the locale "en-US". If a type of parameter is string, 275 * ilib will take only well-formed BCP-47 tag <p> 276 * 277 * 278 * @static 279 * @param {string|undefined|null} spec the locale specifier for the default locale 280 */ 281 ilib.setLocale = function (spec) { 282 if (typeof(spec) === 'string' || !spec) { 283 ilib.locale = spec; 284 } 285 // else ignore other data types, as we don't have the dependencies 286 // to look into them to find a locale 287 }; 288 289 /** 290 * @private 291 */ 292 function parseLocale(str) { 293 if (!str) return str; 294 295 // take care of the libc style locale with a dot + script at the end 296 var dot = str.indexOf('.') 297 if (dot > -1) { 298 str = str.substring(0, dot); 299 } 300 301 // handle the posix default locale 302 if (str === "C") return "en-US"; 303 304 var parts = str.replace(/_/g, '-').split(/-/g); 305 var localeParts = []; 306 307 if (parts.length > 0) { 308 if (parts[0].length === 2 || parts[0].length === 3) { 309 // language 310 localeParts.push(parts[0].toLowerCase()); 311 312 if (parts.length > 1) { 313 if (parts[1].length === 4) { 314 // script 315 localeParts.push(parts[1][0].toUpperCase() + parts[1].substring(1).toLowerCase()); 316 } else if (parts[1].length === 2 || parts[1].length == 3) { 317 // region 318 localeParts.push(parts[1].toUpperCase()); 319 } 320 321 if (parts.length > 2) { 322 if (parts[2].length === 2 || parts[2].length == 3) { 323 // region 324 localeParts.push(parts[2].toUpperCase()); 325 } 326 } 327 } 328 } 329 } 330 331 return localeParts.join('-'); 332 } 333 334 /** 335 * Return the default locale for all of ilib if one has been set. This 336 * locale will be used when no explicit locale is passed to any ilib 337 * class. If the default 338 * locale is not set, ilib will attempt to use the locale of the 339 * environment it is running in, if it can find that. If not, it will 340 * default to the locale "en-US".<p> 341 * 342 * 343 * @static 344 * @return {string} the locale specifier for the default locale 345 */ 346 ilib.getLocale = function () { 347 var lang, dot; 348 if (typeof(ilib.locale) !== 'string') { 349 var plat = ilib._getPlatform(); 350 switch (plat) { 351 case 'browser': 352 // running in a browser 353 if(typeof(navigator.language) !== 'undefined') { 354 ilib.locale = parseLocale(navigator.language); // FF/Opera/Chrome/Webkit 355 } 356 if (!ilib.locale) { 357 // IE on Windows 358 lang = typeof(navigator.browserLanguage) !== 'undefined' ? 359 navigator.browserLanguage : 360 (typeof(navigator.userLanguage) !== 'undefined' ? 361 navigator.userLanguage : 362 (typeof(navigator.systemLanguage) !== 'undefined' ? 363 navigator.systemLanguage : 364 undefined)); 365 if (typeof(lang) !== 'undefined' && lang) { 366 // for some reason, MS uses lower case region tags 367 ilib.locale = parseLocale(lang); 368 } else { 369 ilib.locale = undefined; 370 } 371 } 372 break; 373 case 'webos-webapp': 374 case 'webos': 375 // webOS 376 if (typeof(PalmSystem.locales) !== 'undefined' && 377 typeof(PalmSystem.locales.UI) != 'undefined' && 378 PalmSystem.locales.UI.length > 0) { 379 ilib.locale = parseLocale(PalmSystem.locales.UI); 380 } else if (typeof(PalmSystem.locale) !== 'undefined') { 381 ilib.locale = parseLocale(PalmSystem.locale); 382 } else { 383 ilib.locale = undefined; 384 } 385 break; 386 case 'rhino': 387 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 388 // running under plain rhino 389 var l = [environment.user.language]; 390 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 391 l.push(environment.user.country); 392 } 393 ilib.locale = l.join("-"); 394 } 395 break; 396 case "trireme": 397 // under trireme on rhino emulating nodejs 398 lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 399 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 400 // where language and region are the correct ISO codes separated by 401 // an underscore. This translate it back to the BCP-47 form. 402 ilib.locale = parseLocale(lang); 403 break; 404 case 'nodejs': 405 // running under nodejs 406 lang = global.process.env.LANG || global.process.env.LC_ALL; 407 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 408 // where language and region are the correct ISO codes separated by 409 // an underscore. This translate it back to the BCP-47 form. 410 ilib.locale = parseLocale(lang); 411 break; 412 case 'qt': 413 // running in the Javascript engine under Qt/QML 414 var locobj = Qt.locale(); 415 ilib.locale = parseLocale(locobj.name || "en-US"); 416 break; 417 } 418 // test for posix "C" locale 419 ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale.length && ilib.locale !== "C" ? ilib.locale : 'en-US'; 420 if (ilib.locale === "en") { 421 ilib.locale = "en-US"; // hack to get various platforms working correctly 422 } 423 } 424 return ilib.locale; 425 }; 426 427 /** 428 * Sets the default time zone for all of ilib. This time zone will be used when 429 * no explicit time zone is passed to any ilib class. If the default time zone 430 * is not set, ilib will attempt to use the time zone of the 431 * environment it is running in, if it can find that. If not, it will 432 * default to the the UTC zone "Etc/UTC".<p> 433 * 434 * 435 * @static 436 * @param {string} tz the name of the time zone to set as the default time zone 437 */ 438 ilib.setTimeZone = function (tz) { 439 ilib.tz = tz || ilib.tz; 440 }; 441 442 /** 443 * Return the default time zone for all of ilib if one has been set. This 444 * time zone will be used when no explicit time zone is passed to any ilib 445 * class. If the default time zone 446 * is not set, ilib will attempt to use the locale of the 447 * environment it is running in, if it can find that. If not, it will 448 * default to the the zone "local".<p> 449 * 450 * 451 * @static 452 * @return {string} the default time zone for ilib 453 */ 454 ilib.getTimeZone = function() { 455 if (typeof(ilib.tz) === 'undefined') { 456 if (typeof(Intl) !== 'undefined' && typeof(Intl.DateTimeFormat) !== 'undefined') { 457 var ro = new Intl.DateTimeFormat().resolvedOptions(); 458 ilib.tz = ro && ro.timeZone; 459 return ilib.tz; 460 } 461 462 switch (ilib._getPlatform()) { 463 case 'browser': 464 // running in a browser 465 if (navigator.timezone && navigator.timezone.length > 0) { 466 ilib.tz = navigator.timezone; 467 } 468 break; 469 case 'webos-webapp': 470 case 'webos': 471 // running in webkit on webOS 472 if (PalmSystem.timezone && PalmSystem.timezone.length > 0) { 473 ilib.tz = PalmSystem.timezone; 474 } 475 break; 476 case 'rhino': 477 // running under rhino 478 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 479 ilib.tz = environment.user.timezone; 480 } 481 break; 482 case 'nodejs': 483 if (global.process.env && typeof(global.process.env.TZ) !== "undefined") { 484 ilib.tz = global.process.env.TZ; 485 } 486 break; 487 } 488 489 ilib.tz = ilib.tz || "local"; 490 } 491 492 return ilib.tz; 493 }; 494 495 /** 496 * @class 497 * Defines the interface for the loader class for ilib. The main method of the 498 * loader object is loadFiles(), which loads a set of requested locale data files 499 * from where-ever it is stored. 500 * @interface 501 */ 502 ilib.Loader = function() {}; 503 504 /** 505 * Load a set of files from where-ever it is stored.<p> 506 * 507 * This is the main function define a callback function for loading missing locale 508 * data or resources. 509 * If this copy of ilib is assembled without including the required locale data 510 * or resources, then that data can be lazy loaded dynamically when it is 511 * needed by calling this method. Each ilib class will first 512 * check for the existence of data under ilib.data, and if it is not there, 513 * it will attempt to load it by calling this method of the loader, and then place 514 * it there.<p> 515 * 516 * Suggested implementations of this method might load files 517 * directly from disk under nodejs or rhino, or within web pages, to load 518 * files from the server with XHR calls.<p> 519 * 520 * The first parameter to this method, paths, is an array of relative paths within 521 * the ilib dir structure for the 522 * requested data. These paths will already have the locale spec integrated 523 * into them, so no further tweaking needs to happen to load the data. Simply 524 * load the named files. The second 525 * parameter tells the loader whether to load the files synchronously or asynchronously. 526 * If the sync parameters is false, then the onLoad function must also be specified. 527 * The third parameter gives extra parameters to the loader passed from the calling 528 * code. This may contain any property/value pairs. The last parameter, callback, 529 * is a callback function to call when all of the data is finishing loading. Make 530 * sure to call the callback with the context of "this" so that the caller has their 531 * context back again.<p> 532 * 533 * The loader function must be able to operate either synchronously or asychronously. 534 * If the loader function is called with an undefined callback function, it is 535 * expected to load the data synchronously, convert it to javascript 536 * objects, and return the array of json objects as the return value of the 537 * function. If the loader 538 * function is called with a callback function, it may load the data 539 * synchronously or asynchronously (doesn't matter which) as long as it calls 540 * the callback function with the data converted to a javascript objects 541 * when it becomes available. If a particular file could not be loaded, the 542 * loader function should put undefined into the corresponding entry in the 543 * results array. 544 * Note that it is important that all the data is loaded before the callback 545 * is called.<p> 546 * 547 * An example implementation for nodejs might be: 548 * 549 * <pre> 550 * var fs = require("fs"); 551 * 552 * var myLoader = function() {}; 553 * myLoader.prototype = new Loader(); 554 * myLoader.prototype.constructor = myLoader; 555 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 556 * if (sync) { 557 * var ret = []; 558 * // synchronous load -- just return the result 559 * paths.forEach(function (path) { 560 * var json = fs.readFileSync(path, "utf-8"); 561 * ret.push(json ? JSON.parse(json) : undefined); 562 * }); 563 * 564 * return ret; 565 * } 566 * this.callback = callback; 567 * 568 * // asynchronous 569 * this.results = []; 570 * this._loadFilesAsync(paths); 571 * } 572 * myLoader.prototype._loadFilesAsync = function (paths) { 573 * if (paths.length > 0) { 574 * var file = paths.shift(); 575 * fs.readFile(file, "utf-8", function(err, json) { 576 * this.results.push(err ? undefined : JSON.parse(json)); 577 * // call self recursively so that the callback is only called at the end 578 * // when all the files are loaded sequentially 579 * if (paths.length > 0) { 580 * this._loadFilesAsync(paths); 581 * } else { 582 * this.callback(this.results); 583 * } 584 * }); 585 * } 586 * } 587 * 588 * // bind to "this" so that "this" is relative to your own instance 589 * ilib.setLoaderCallback(new myLoader()); 590 * </pre> 591 592 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 593 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 594 * @param {Object} params an object with any extra parameters for the loader. These can be 595 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 596 * calls ilib and the code that provides the loader are together and can have a private 597 * agreement between them about what the parameters should contain. 598 * @param {function(Object)} callback function to call when the files are all loaded. The 599 * parameter of the callback function is the contents of the files. 600 */ 601 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 602 603 /** 604 * Return all files available for loading using this loader instance. 605 * This method returns an object where the properties are the paths to 606 * directories where files are loaded from and the values are an array 607 * of strings containing the relative paths under the directory of each 608 * file that can be loaded.<p> 609 * 610 * Example: 611 * <pre> 612 * { 613 * "/usr/share/javascript/ilib/locale": [ 614 * "dateformats.json", 615 * "aa/dateformats.json", 616 * "af/dateformats.json", 617 * "agq/dateformats.json", 618 * "ak/dateformats.json", 619 * ... 620 * "zxx/dateformats.json" 621 * ] 622 * } 623 * </pre> 624 * @returns {Object} a hash containing directory names and 625 * paths to file that can be loaded by this loader 626 */ 627 ilib.Loader.prototype.listAvailableFiles = function() {}; 628 629 /** 630 * Return true if the file in the named path is available for loading using 631 * this loader. The path may be given as an absolute path, in which case 632 * only that file is checked, or as a relative path, in which case, the 633 * relative path may appear underneath any of the directories that the loader 634 * knows about. 635 * @returns {boolean} true if the file in the named path is available for loading, and 636 * false otherwise 637 */ 638 ilib.Loader.prototype.isAvailable = function(path) {}; 639 640 /** 641 * Set the custom loader used to load ilib's locale data in your environment. 642 * The instance passed in must implement the Loader interface. See the 643 * Loader class documentation for more information about loaders. 644 * 645 * @static 646 * @param {ilib.Loader} loader class to call to access the requested data. 647 * @return {boolean} true if the loader was installed correctly, or false 648 * if not 649 */ 650 ilib.setLoaderCallback = function(loader) { 651 // only a basic check 652 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 653 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 654 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 655 ilib._load = loader; 656 return true; 657 } 658 return false; 659 }; 660 661 /** 662 * Return the custom Loader instance currently in use with this instance 663 * of ilib. If there is no loader, this method returns undefined. 664 * 665 * @protected 666 * @static 667 * @return {ilib.Loader|undefined} the loader instance currently in use, or 668 * undefined if there is no such loader 669 */ 670 ilib.getLoader = function() { 671 return ilib._load; 672 }; 673 674 /** 675 * Test whether an object is an javascript array. 676 * 677 * @static 678 * @param {*} object The object to test 679 * @return {boolean} return true if the object is an array 680 * and false otherwise 681 */ 682 ilib.isArray = function(object) { 683 if (typeof(object) === 'object') { 684 return Object.prototype.toString.call(object) === '[object Array]'; 685 } 686 return false; 687 }; 688 689 /** 690 * Extend object1 by mixing in everything from object2 into it. The objects 691 * are deeply extended, meaning that this method recursively descends the 692 * tree in the objects and mixes them in at each level. Arrays are extended 693 * by concatenating the elements of object2 onto those of object1. 694 * 695 * @static 696 * @param {Object} object1 the target object to extend 697 * @param {Object=} object2 the object to mix in to object1 698 * @return {Object} returns object1 699 */ 700 ilib.extend = function (object1, object2) { 701 var prop = undefined; 702 if (object2) { 703 for (prop in object2) { 704 // don't extend object with undefined or functions 705 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 706 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 707 //console.log("Merging array prop " + prop); 708 object1[prop] = object1[prop].concat(object2[prop]); 709 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 710 //console.log("Merging object prop " + prop); 711 if (prop !== "ilib") { 712 object1[prop] = ilib.extend(object1[prop], object2[prop]); 713 } 714 } else { 715 //console.log("Copying prop " + prop); 716 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 717 object1[prop] = object2[prop]; 718 } 719 } 720 } 721 } 722 return object1; 723 }; 724 725 ilib.extend2 = function (object1, object2) { 726 var prop = undefined; 727 if (object2) { 728 for (prop in object2) { 729 // don't extend object with undefined or functions 730 if (prop && typeof(object2[prop]) !== 'undefined') { 731 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 732 //console.log("Merging array prop " + prop); 733 object1[prop] = object1[prop].concat(object2[prop]); 734 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 735 //console.log("Merging object prop " + prop); 736 if (prop !== "ilib") { 737 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 738 } 739 } else { 740 //console.log("Copying prop " + prop); 741 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 742 object1[prop] = object2[prop]; 743 } 744 } 745 } 746 } 747 return object1; 748 }; 749 750 /** 751 * If Function.prototype.bind does not exist in this JS engine, this 752 * function reimplements it in terms of older JS functions. 753 * bind() doesn't exist in many older browsers. 754 * 755 * @static 756 * @param {Object} scope object that the method should operate on 757 * @param {function(...)} method method to call 758 * @return {function(...)|undefined} function that calls the given method 759 * in the given scope with all of its arguments properly attached, or 760 * undefined if there was a problem with the arguments 761 */ 762 ilib.bind = function(scope, method/*, bound arguments*/){ 763 if (!scope || !method) { 764 return undefined; 765 } 766 767 /** @protected 768 * @param {Arguments} inArrayLike 769 * @param {number=} inOffset 770 */ 771 function cloneArray(inArrayLike, inOffset) { 772 var arr = []; 773 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 774 arr.push(inArrayLike[i]); 775 } 776 return arr; 777 } 778 779 if (typeof(method) === 'function') { 780 var func, args = cloneArray(arguments, 2); 781 if (typeof(method.bind) === 'function') { 782 func = method.bind.apply(method, [scope].concat(args)); 783 } else { 784 func = function() { 785 var nargs = cloneArray(arguments); 786 // invoke with collected args 787 return method.apply(scope, args.concat(nargs)); 788 }; 789 } 790 return func; 791 } 792 return undefined; 793 }; 794 795 /** 796 * @private 797 */ 798 ilib._dyncode = false; 799 800 /** 801 * Return true if this copy of ilib is using dynamically loaded code. It returns 802 * false for pre-assembled code. 803 * 804 * @static 805 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 806 */ 807 ilib.isDynCode = function() { 808 return ilib._dyncode; 809 }; 810 811 /** 812 * @private 813 */ 814 ilib._dyndata = false; 815 816 /** 817 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 818 * false for pre-assembled data. 819 * 820 * @static 821 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 822 */ 823 ilib.isDynData = function() { 824 return ilib._dyndata; 825 }; 826 827 /** 828 * When true, this will cause ilib to cache merged locale data. Merged data is created 829 * whenever a locale is specified where the data for a locale data resides in various 830 * files, and these need to be merged together to create the overall data for that locale.<p> 831 * 832 * For example, if the ilib locale is "fr-CA", the final locale data is assembled from the 833 * following locale parts: 834 * 835 * <ul> 836 * <li>root - the root/default locale data shared by every locale 837 * <li>fr - the locale data shared by every flavour of French (eg. translations or date formats) 838 * <li>und/CA - the language-independent locale data for Canada (eg. time zone or official currency) 839 * <li>fr/CA - the locale data that is unique to French for Canada (eg. date or currency formats) 840 * </ul> 841 * 842 * On some platforms, the data loaded from disk is cached and then merged each time it is 843 * needed to create the whole locale data for the current locale. In other platforms, the 844 * merging is too slow, so the already-merged data is cached as well after the first time it 845 * is requested. In this way, we sacrifice the memory footprint for the sake of speed. 846 */ 847 848 ilib._cacheMerged = false; 849 850 ilib._loadtime = new Date().getTime(); 851