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