diff --git a/build.properties b/build.properties index 68dbfbc63c..54c7f64cb4 100644 --- a/build.properties +++ b/build.properties @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=14.0.0 +version=14.1.0 diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index fb5baa432e..08a4ba4b77 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,6 +1,23 @@ Release Notes for Version 14 ============================ +Build 002 +------- +Published as version 14.1.0 + +New Features: +* New top-level entry point + * You can now require("ilib") directly instead of requiring the file that installs the loader. (ie. + ilib/lib/ilib-node.js for node, or ilib/lib/ilib-qt.js for qt, etc.) + * The top-level entry point will figure out which environment it is running in and automatically install + the appropriate loader. + * Allows ilib to run in node or within a webpack bundle without changes + * Allows clients to just require ilib classes directly without first requiring the loader installer + +Bug Fixes +* Restored a missing mapping from the the native name for "Japan" to the ISO code "JP" in the nativecountries.json + * Fixes address parsing for Japan + Build 001 ------- Published as version 14.0.0 diff --git a/js/build.xml b/js/build.xml index f33dbea127..1c2dc53e54 100644 --- a/js/build.xml +++ b/js/build.xml @@ -342,8 +342,10 @@ limitations under the License. - + + + @@ -467,6 +469,7 @@ limitations under the License. + @@ -494,6 +497,7 @@ limitations under the License. + @@ -506,6 +510,11 @@ limitations under the License. + + + + + diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000000..eaadc3168e --- /dev/null +++ b/js/index.js @@ -0,0 +1,57 @@ +/* + * index.js - top level entry point for ilib + * + * Copyright © 2018 JEDLSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var ilib = require("./lib/ilib.js"); + +if (!ilib._platform || (typeof(ilib._dyndata) !== 'boolean' && typeof(ilib._dyncode) !== 'boolean')) { + if (typeof(__webpack_require__) !== 'undefined') { + // The following will either require and then install the + // WebpackLoader to dynamically load locale data bundles, + // or it will statically require all of the locale data that + // this build needs so that it can be included into this + // webpack bundle. + + // !defineLocaleData + } else { + switch (ilib._getPlatform()) { + case 'webos': + case 'nodejs': + require("./lib/ilib-node.js"); + break; + + case 'qt': + require("./lib/ilib-qt.js"); + + case 'rhino': + require("./lib/ilib-rhino.js"); + break; + + case 'ringo': + require("./lib/ilib-ringo.js"); + break; + + default: + ilib._dyncode = false; + ilib._dyndata = false; + break; + } + } +} + +module.exports = ilib; diff --git a/js/lib/Address.js b/js/lib/Address.js index 4af9de64f4..c66f93a185 100644 --- a/js/lib/Address.js +++ b/js/lib/Address.js @@ -1,6 +1,6 @@ /* * Address.js - Represent a mailing address - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ // !data address countries nativecountries ctrynames -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); @@ -34,580 +34,580 @@ var IString = require("./IString.js"); /** * @class * Create a new Address instance and parse a physical address.

- * - * This function parses a physical address written in a free-form string. - * It returns an object with a number of properties from the list below + * + * This function parses a physical address written in a free-form string. + * It returns an object with a number of properties from the list below * that it may have extracted from that address.

- * + * * The following is a list of properties that the algorithm will return:

- * + * *

    *
  • streetAddress: The street address, including house numbers and all. - *
  • locality: The locality of this address (usually a city or town). + *
  • locality: The locality of this address (usually a city or town). *
  • region: The region where the locality is located. In the US, this * corresponds to states. In other countries, this may be provinces, * cantons, prefectures, etc. In some smaller countries, there are no * such divisions. - *
  • postalCode: Country-specific code for expediting mail. In the US, + *
  • postalCode: Country-specific code for expediting mail. In the US, * this is the zip code. *
  • country: The country of the address. *
  • countryCode: The ISO 3166 2-letter region code for the destination * country in this address. - *
- * - * The above properties will not necessarily appear in the instance. For - * any individual property, if the free-form address does not contain + * + * + * The above properties will not necessarily appear in the instance. For + * any individual property, if the free-form address does not contain * that property or it cannot be parsed out, the it is left out.

- * + * * The options parameter may contain any of the following properties: - * + * *

    - *
  • locale - locale or localeSpec to use to parse the address. If not + *
  • locale - locale or localeSpec to use to parse the address. If not * specified, this function will use the current ilib locale - * + * *
  • onLoad - a callback function to call when the address info for the - * locale is fully loaded and the address has been parsed. When the onLoad - * option is given, the address object + * locale is fully loaded and the address has been parsed. When the onLoad + * option is given, the address object * will attempt to load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * + * * When an address cannot be parsed properly, the entire address will be placed * into the streetAddress property.

- * + * * When the freeformAddress is another Address, this will act like a copy * constructor.

- * - * + * + * * @constructor * @param {string|Address} freeformAddress free-form address to parse, or a * javascript object containing the fields * @param {Object} options options to the parser */ var Address = function (freeformAddress, options) { - var address; - - if (!freeformAddress) { - return undefined; - } - - this.sync = true; - this.loadParams = {}; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - if (options.loadParams) { - this.loadParams = options.loadParams; - } - } - - this.locale = this.locale || new Locale(); - // initialize from an already parsed object - if (typeof(freeformAddress) === 'object') { - /** - * The street address, including house numbers and all. - * @type {string|undefined} - */ - this.streetAddress = freeformAddress.streetAddress; - /** - * The locality of this address (usually a city or town). - * @type {string|undefined} - */ - this.locality = freeformAddress.locality; - /** - * The region (province, canton, prefecture, state, etc.) where the address is located. - * @type {string|undefined} - */ - this.region = freeformAddress.region; - /** - * Country-specific code for expediting mail. In the US, this is the zip code. - * @type {string|undefined} - */ - this.postalCode = freeformAddress.postalCode; - /** - * Optional city-specific code for a particular post office, used to expidite - * delivery. - * @type {string|undefined} - */ - this.postOffice = freeformAddress.postOffice; - /** - * The country of the address. - * @type {string|undefined} - */ - this.country = freeformAddress.country; - if (freeformAddress.countryCode) { - /** - * The 2 or 3 letter ISO 3166 region code for the destination country in this address. - * @type {string} - */ - this.countryCode = freeformAddress.countryCode; - } - if (freeformAddress.format) { - /** - * private - * @type {string} - */ - this.format = freeformAddress.format; - } - return this; - } - - address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); - address = address.replace(/[\s\n]+$/, ""); - address = address.replace(/^[\s\n]+/, ""); - //console.log("\n\n-------------\nAddress is '" + address + "'"); - - this.lines = address.split(/[,,\n]/g); - this.removeEmptyLines(this.lines); - - isAscii._init(this.sync, this.loadParams, ilib.bind(this, function() { - isIdeo._init(this.sync, this.loadParams, ilib.bind(this, function() { - isDigit._init(this.sync, this.loadParams, ilib.bind(this, function() { - if (typeof(ilib.data.nativecountries) === 'undefined') { - Utils.loadData({ - object: "Address", - name: "nativecountries.json", // countries in their own language - locale: "-", // only need to load the root file - nonlocale: true, - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(nativecountries) { - ilib.data.nativecountries = nativecountries; - this._loadCountries(options && options.onLoad); - }) - }); - } else { - this._loadCountries(options && options.onLoad); - } - })); - })); - })); + var address; + + if (!freeformAddress) { + return undefined; + } + + this.sync = true; + this.loadParams = {}; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + if (options.loadParams) { + this.loadParams = options.loadParams; + } + } + + this.locale = this.locale || new Locale(); + // initialize from an already parsed object + if (typeof(freeformAddress) === 'object') { + /** + * The street address, including house numbers and all. + * @type {string|undefined} + */ + this.streetAddress = freeformAddress.streetAddress; + /** + * The locality of this address (usually a city or town). + * @type {string|undefined} + */ + this.locality = freeformAddress.locality; + /** + * The region (province, canton, prefecture, state, etc.) where the address is located. + * @type {string|undefined} + */ + this.region = freeformAddress.region; + /** + * Country-specific code for expediting mail. In the US, this is the zip code. + * @type {string|undefined} + */ + this.postalCode = freeformAddress.postalCode; + /** + * Optional city-specific code for a particular post office, used to expidite + * delivery. + * @type {string|undefined} + */ + this.postOffice = freeformAddress.postOffice; + /** + * The country of the address. + * @type {string|undefined} + */ + this.country = freeformAddress.country; + if (freeformAddress.countryCode) { + /** + * The 2 or 3 letter ISO 3166 region code for the destination country in this address. + * @type {string} + */ + this.countryCode = freeformAddress.countryCode; + } + if (freeformAddress.format) { + /** + * private + * @type {string} + */ + this.format = freeformAddress.format; + } + return this; + } + + address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); + address = address.replace(/[\s\n]+$/, ""); + address = address.replace(/^[\s\n]+/, ""); + //console.log("\n\n-------------\nAddress is '" + address + "'"); + + this.lines = address.split(/[,,\n]/g); + this.removeEmptyLines(this.lines); + + isAscii._init(this.sync, this.loadParams, ilib.bind(this, function() { + isIdeo._init(this.sync, this.loadParams, ilib.bind(this, function() { + isDigit._init(this.sync, this.loadParams, ilib.bind(this, function() { + if (typeof(ilib.data.nativecountries) === 'undefined') { + Utils.loadData({ + object: "Address", + name: "nativecountries.json", // countries in their own language + locale: "-", // only need to load the root file + nonlocale: true, + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(nativecountries) { + ilib.data.nativecountries = nativecountries; + this._loadCountries(options && options.onLoad); + }) + }); + } else { + this._loadCountries(options && options.onLoad); + } + })); + })); + })); }; /** @protected */ Address.prototype = { - /** - * @private - */ - _loadCountries: function(onLoad) { - if (typeof(ilib.data.countries) === 'undefined') { - Utils.loadData({ - object: "Address", - name: "countries.json", // countries in English - locale: "-", // only need to load the root file - nonlocale: true, - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(countries) { - ilib.data.countries = countries; - this._loadCtrynames(onLoad); - }) - }); - } else { - this._loadCtrynames(onLoad); - } - }, - - /** - * @private - */ - _loadCtrynames: function(onLoad) { - Utils.loadData({ - name: "ctrynames.json", - object: "Address", - locale: this.locale, - sync: this.sync, - loadParams: JSUtils.merge(this.loadParams, {returnOne: true}), - callback: ilib.bind(this, function(ctrynames) { - this.ctrynames = ctrynames; - this._determineDest(ctrynames, onLoad); - }) - }); - }, - - /** - * @private - * @param {Object?} ctrynames - */ - _findDest: function (ctrynames) { - var match; - - for (var countryName in ctrynames) { - if (countryName && countryName !== "generated") { - // find the longest match in the current table - // ctrynames contains the country names mapped to region code - // for efficiency, only test for things longer than the current match - if (!match || match.text.length < countryName.length) { - var temp = this._findCountry(countryName); - if (temp) { - match = temp; - this.country = match.text; - this.countryCode = ctrynames[countryName]; - } - } - } - } - return match; - }, - - /** - * @private - * @param {Object?} localizedCountries - * @param {function(Address):undefined} callback - */ - _determineDest: function (localizedCountries, callback) { - var match; - - /* - * First, find the name of the destination country, as that determines how to parse - * the rest of the address. For any address, there are three possible ways - * that the name of the country could be written: - * 1. In the current language - * 2. In its own native language - * 3. In English - * We'll try all three. - */ - var tables = []; - if (localizedCountries) { - tables.push(localizedCountries); - } - tables.push(ilib.data.nativecountries); - tables.push(ilib.data.countries); - - for (var i = 0; i < tables.length; i++) { - match = this._findDest(tables[i]); - - if (match) { - this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); - - this._init(callback); - return; - } - } - - // no country, so try parsing it as if we were in the same country - this.country = undefined; - this.countryCode = this.locale.getRegion(); - this._init(callback); - }, - - /** - * @private - * @param {function(Address):undefined} callback - */ - _init: function(callback) { - Utils.loadData({ - object: "Address", - locale: new Locale(this.countryCode), - name: "address.json", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(info) { - if (!info || JSUtils.isEmpty(info) || !info.fields) { - // load the "unknown" locale instead - Utils.loadData({ - object: "Address", - locale: new Locale("XX"), - name: "address.json", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(info) { - this.info = info; - this._parseAddress(); - if (typeof(callback) === 'function') { - callback(this); - } - }) - }); - } else { - this.info = info; - this._parseAddress(); - if (typeof(callback) === 'function') { - callback(this); - } - } - }) - }); - }, - - /** - * @private - */ - _parseAddress: function() { - // clean it up first - var i, - asianChars = 0, - latinChars = 0, - startAt, - infoFields, - field, - pattern, - matchFunction, - match, - fieldNumber; - - // for locales that support both latin and asian character addresses, - // decide if we are parsing an asian or latin script address - if (this.info && this.info.multiformat) { - for (var j = 0; j < this.lines.length; j++) { - var line = new IString(this.lines[j]); - var it = line.charIterator(); - while (it.hasNext()) { - var c = it.next(); - if (isIdeo(c) || - CType.withinRange(c, "hangul") || - CType.withinRange(c, 'katakana') || - CType.withinRange(c, 'hiragana') || - CType.withinRange(c, 'bopomofo')) { - asianChars++; - } else if (isAscii(c) && !isDigit(c)) { - latinChars++; - } - } - } - - this.format = (asianChars >= latinChars) ? "asian" : "latin"; - startAt = this.info.startAt[this.format]; - infoFields = this.info.fields[this.format]; - // //console.log("multiformat locale: format is now " + this.format); - } else { - startAt = (this.info && this.info.startAt) || "end"; - infoFields = (this.info && this.info.fields) || []; - } - this.compare = (startAt === "end") ? this.endsWith : this.startsWith; - - //console.log("this.lines is: " + JSON.stringify(this.lines)); - - for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { - field = infoFields[i]; - this.removeEmptyLines(this.lines); - //console.log("Searching for field " + field.name); - if (field.pattern) { - if (typeof(field.pattern) === 'string') { - pattern = new RegExp(field.pattern, "img"); - matchFunction = this.matchRegExp; - } else { - pattern = field.pattern; - matchFunction = this.matchPattern; - } - - switch (field.line) { - case 'startAtFirst': - for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { - match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); - if (match) { - break; - } - } - break; - case 'startAtLast': - for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { - match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); - if (match) { - break; - } - } - break; - case 'first': - fieldNumber = 0; - match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); - break; - case 'last': - default: - fieldNumber = this.lines.length - 1; - match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); - break; - } - if (match) { - // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); - // //console.log("remaining line is " + match.line); - this.lines[fieldNumber] = match.line; - this[field.name] = match.match; - } - } else { - // if nothing is given, default to taking the whole field - this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); - //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); - } - } - - // all the left overs go in the street address field - this.removeEmptyLines(this.lines); - if (this.lines.length > 0) { - //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); - // Korea uses spaces between words, despite being an "asian" locale - var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); - this.streetAddress = this.lines.join(joinString).trim(); - } - - this.lines = undefined; - //console.log("final result is " + JSON.stringify(this)); - }, - - /** - * @protected - * Find the named country either at the end or the beginning of the address. - */ - _findCountry: function(name) { - var start = -1, match, line = 0; - - if (this.lines.length > 0) { - start = this.startsWith(this.lines[line], name); - if (start === -1) { - line = this.lines.length-1; - start = this.endsWith(this.lines[line], name); - } - if (start !== -1) { - match = { - text: this.lines[line].substring(start, start + name.length), - line: line, - start: start - }; - } - } - - return match; - }, - - endsWith: function (subject, query) { - var start = subject.length-query.length, - i, - pat; - //console.log("endsWith: checking " + query + " against " + subject); - for (i = 0; i < query.length; i++) { - // TODO: use case mapper instead of toLowerCase() - if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { - return -1; - } - } - if (start > 0) { - pat = /\s/; - if (!pat.test(subject.charAt(start-1))) { - // make sure if we are not at the beginning of the string, that the match is - // not the end of some other word - return -1; - } - } - return start; - }, - - startsWith: function (subject, query) { - var i; - // //console.log("startsWith: checking " + query + " against " + subject); - for (i = 0; i < query.length; i++) { - // TODO: use case mapper instead of toLowerCase() - if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { - return -1; - } - } - return 0; - }, - - removeEmptyLines: function (arr) { - var i = 0; - - while (i < arr.length) { - if (arr[i]) { - arr[i] = arr[i].trim(); - if (arr[i].length === 0) { - arr.splice(i,1); - } else { - i++; - } - } else { - arr.splice(i,1); - } - } - }, - - matchRegExp: function(address, line, expression, matchGroup, startAt) { - var lastMatch, - match, - ret = {}, - last; - - //console.log("searching for regexp " + expression.source + " in line " + line); - - match = expression.exec(line); - if (startAt === 'end') { - while (match !== null && match.length > 0) { - //console.log("found matches " + JSON.stringify(match)); - lastMatch = match; - match = expression.exec(line); - } - match = lastMatch; - } - - if (match && match !== null) { - //console.log("found matches " + JSON.stringify(match)); - matchGroup = matchGroup || 0; - if (match[matchGroup] !== undefined) { - ret.match = match[matchGroup].trim(); - ret.match = ret.match.replace(/^\-|\-+$/, ''); - ret.match = ret.match.replace(/\s+$/, ''); - last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); - //console.log("last is " + last); - ret.line = line.slice(0,last); - if (address.format !== "asian") { - ret.line += " "; - } - ret.line += line.slice(last+match[matchGroup].length); - ret.line = ret.line.trim(); - //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); - return ret; - } - //} else { - //console.log("no match"); - } - - return undefined; - }, - - matchPattern: function(address, line, pattern, matchGroup) { - var start, - j, - ret = {}; - - //console.log("searching in line " + line); - - // search an array of possible fixed strings - //console.log("Using fixed set of strings."); - for (j = 0; j < pattern.length; j++) { - start = address.compare(line, pattern[j]); - if (start !== -1) { + /** + * @private + */ + _loadCountries: function(onLoad) { + if (typeof(ilib.data.countries) === 'undefined') { + Utils.loadData({ + object: "Address", + name: "countries.json", // countries in English + locale: "-", // only need to load the root file + nonlocale: true, + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(countries) { + ilib.data.countries = countries; + this._loadCtrynames(onLoad); + }) + }); + } else { + this._loadCtrynames(onLoad); + } + }, + + /** + * @private + */ + _loadCtrynames: function(onLoad) { + Utils.loadData({ + name: "ctrynames.json", + object: "Address", + locale: this.locale, + sync: this.sync, + loadParams: JSUtils.merge(this.loadParams, {returnOne: true}), + callback: ilib.bind(this, function(ctrynames) { + this.ctrynames = ctrynames; + this._determineDest(ctrynames, onLoad); + }) + }); + }, + + /** + * @private + * @param {Object?} ctrynames + */ + _findDest: function (ctrynames) { + var match; + + for (var countryName in ctrynames) { + if (countryName && countryName !== "generated") { + // find the longest match in the current table + // ctrynames contains the country names mapped to region code + // for efficiency, only test for things longer than the current match + if (!match || match.text.length < countryName.length) { + var temp = this._findCountry(countryName); + if (temp) { + match = temp; + this.country = match.text; + this.countryCode = ctrynames[countryName]; + } + } + } + } + return match; + }, + + /** + * @private + * @param {Object?} localizedCountries + * @param {function(Address):undefined} callback + */ + _determineDest: function (localizedCountries, callback) { + var match; + + /* + * First, find the name of the destination country, as that determines how to parse + * the rest of the address. For any address, there are three possible ways + * that the name of the country could be written: + * 1. In the current language + * 2. In its own native language + * 3. In English + * We'll try all three. + */ + var tables = []; + if (localizedCountries) { + tables.push(localizedCountries); + } + tables.push(ilib.data.nativecountries); + tables.push(ilib.data.countries); + + for (var i = 0; i < tables.length; i++) { + match = this._findDest(tables[i]); + + if (match) { + this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); + + this._init(callback); + return; + } + } + + // no country, so try parsing it as if we were in the same country + this.country = undefined; + this.countryCode = this.locale.getRegion(); + this._init(callback); + }, + + /** + * @private + * @param {function(Address):undefined} callback + */ + _init: function(callback) { + Utils.loadData({ + object: "Address", + locale: new Locale(this.countryCode), + name: "address.json", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(info) { + if (!info || JSUtils.isEmpty(info) || !info.fields) { + // load the "unknown" locale instead + Utils.loadData({ + object: "Address", + locale: new Locale("XX"), + name: "address.json", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(info) { + this.info = info; + this._parseAddress(); + if (typeof(callback) === 'function') { + callback(this); + } + }) + }); + } else { + this.info = info; + this._parseAddress(); + if (typeof(callback) === 'function') { + callback(this); + } + } + }) + }); + }, + + /** + * @private + */ + _parseAddress: function() { + // clean it up first + var i, + asianChars = 0, + latinChars = 0, + startAt, + infoFields, + field, + pattern, + matchFunction, + match, + fieldNumber; + + // for locales that support both latin and asian character addresses, + // decide if we are parsing an asian or latin script address + if (this.info && this.info.multiformat) { + for (var j = 0; j < this.lines.length; j++) { + var line = new IString(this.lines[j]); + var it = line.charIterator(); + while (it.hasNext()) { + var c = it.next(); + if (isIdeo(c) || + CType.withinRange(c, "hangul") || + CType.withinRange(c, 'katakana') || + CType.withinRange(c, 'hiragana') || + CType.withinRange(c, 'bopomofo')) { + asianChars++; + } else if (isAscii(c) && !isDigit(c)) { + latinChars++; + } + } + } + + this.format = (asianChars >= latinChars) ? "asian" : "latin"; + startAt = this.info.startAt[this.format]; + infoFields = this.info.fields[this.format]; + // //console.log("multiformat locale: format is now " + this.format); + } else { + startAt = (this.info && this.info.startAt) || "end"; + infoFields = (this.info && this.info.fields) || []; + } + this.compare = (startAt === "end") ? this.endsWith : this.startsWith; + + //console.log("this.lines is: " + JSON.stringify(this.lines)); + + for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { + field = infoFields[i]; + this.removeEmptyLines(this.lines); + //console.log("Searching for field " + field.name); + if (field.pattern) { + if (typeof(field.pattern) === 'string') { + pattern = new RegExp(field.pattern, "img"); + matchFunction = this.matchRegExp; + } else { + pattern = field.pattern; + matchFunction = this.matchPattern; + } + + switch (field.line) { + case 'startAtFirst': + for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { + match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); + if (match) { + break; + } + } + break; + case 'startAtLast': + for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { + match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); + if (match) { + break; + } + } + break; + case 'first': + fieldNumber = 0; + match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); + break; + case 'last': + default: + fieldNumber = this.lines.length - 1; + match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); + break; + } + if (match) { + // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); + // //console.log("remaining line is " + match.line); + this.lines[fieldNumber] = match.line; + this[field.name] = match.match; + } + } else { + // if nothing is given, default to taking the whole field + this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); + //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); + } + } + + // all the left overs go in the street address field + this.removeEmptyLines(this.lines); + if (this.lines.length > 0) { + //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); + // Korea uses spaces between words, despite being an "asian" locale + var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); + this.streetAddress = this.lines.join(joinString).trim(); + } + + this.lines = undefined; + //console.log("final result is " + JSON.stringify(this)); + }, + + /** + * @protected + * Find the named country either at the end or the beginning of the address. + */ + _findCountry: function(name) { + var start = -1, match, line = 0; + + if (this.lines.length > 0) { + start = this.startsWith(this.lines[line], name); + if (start === -1) { + line = this.lines.length-1; + start = this.endsWith(this.lines[line], name); + } + if (start !== -1) { + match = { + text: this.lines[line].substring(start, start + name.length), + line: line, + start: start + }; + } + } + + return match; + }, + + endsWith: function (subject, query) { + var start = subject.length-query.length, + i, + pat; + //console.log("endsWith: checking " + query + " against " + subject); + for (i = 0; i < query.length; i++) { + // TODO: use case mapper instead of toLowerCase() + if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { + return -1; + } + } + if (start > 0) { + pat = /\s/; + if (!pat.test(subject.charAt(start-1))) { + // make sure if we are not at the beginning of the string, that the match is + // not the end of some other word + return -1; + } + } + return start; + }, + + startsWith: function (subject, query) { + var i; + // //console.log("startsWith: checking " + query + " against " + subject); + for (i = 0; i < query.length; i++) { + // TODO: use case mapper instead of toLowerCase() + if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { + return -1; + } + } + return 0; + }, + + removeEmptyLines: function (arr) { + var i = 0; + + while (i < arr.length) { + if (arr[i]) { + arr[i] = arr[i].trim(); + if (arr[i].length === 0) { + arr.splice(i,1); + } else { + i++; + } + } else { + arr.splice(i,1); + } + } + }, + + matchRegExp: function(address, line, expression, matchGroup, startAt) { + var lastMatch, + match, + ret = {}, + last; + + //console.log("searching for regexp " + expression.source + " in line " + line); + + match = expression.exec(line); + if (startAt === 'end') { + while (match !== null && match.length > 0) { + //console.log("found matches " + JSON.stringify(match)); + lastMatch = match; + match = expression.exec(line); + } + match = lastMatch; + } + + if (match && match !== null) { + //console.log("found matches " + JSON.stringify(match)); + matchGroup = matchGroup || 0; + if (match[matchGroup] !== undefined) { + ret.match = match[matchGroup].trim(); + ret.match = ret.match.replace(/^\-|\-+$/, ''); + ret.match = ret.match.replace(/\s+$/, ''); + last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); + //console.log("last is " + last); + ret.line = line.slice(0,last); + if (address.format !== "asian") { + ret.line += " "; + } + ret.line += line.slice(last+match[matchGroup].length); + ret.line = ret.line.trim(); + //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); + return ret; + } + //} else { + //console.log("no match"); + } + + return undefined; + }, + + matchPattern: function(address, line, pattern, matchGroup) { + var start, + j, + ret = {}; + + //console.log("searching in line " + line); + + // search an array of possible fixed strings + //console.log("Using fixed set of strings."); + for (j = 0; j < pattern.length; j++) { + start = address.compare(line, pattern[j]); + if (start !== -1) { ret.match = line.substring(start, start+pattern[j].length); if (start !== 0) { ret.line = line.substring(0,start).trim(); } else { ret.line = line.substring(pattern[j].length).trim(); } - //console.log("found match " + ret.match + " and rest of line is " + ret.line); + //console.log("found match " + ret.match + " and rest of line is " + ret.line); return ret; - } - } - - return undefined; - } + } + } + + return undefined; + } }; module.exports = Address; diff --git a/js/lib/AddressFmt.js b/js/lib/AddressFmt.js index d86331a562..d13bd39f9a 100644 --- a/js/lib/AddressFmt.js +++ b/js/lib/AddressFmt.js @@ -1,6 +1,6 @@ /* * AddressFmt.js - Format an address - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data address addressres regionnames -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -36,117 +36,117 @@ var ResBundle = require("./ResBundle.js"); * *

    *
  • locale - the locale to use to format this address. If not specified, it uses the default locale - * - *
  • style - the style of this address. The default style for each country usually includes all valid + * + *
  • style - the style of this address. The default style for each country usually includes all valid * fields for that country. - * + * *
  • onLoad - a callback function to call when the address info for the - * locale is fully loaded and the address has been parsed. When the onLoad - * option is given, the address formatter object + * locale is fully loaded and the address has been parsed. When the onLoad + * option is given, the address formatter object * will attempt to load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * + * + * * @constructor * @param {Object} options options that configure how this formatter should work * Returns a formatter instance that can format multiple addresses. */ var AddressFmt = function(options) { - this.sync = true; - this.styleName = 'default'; - this.loadParams = {}; - this.locale = new Locale(); - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - if (options.style) { - this.styleName = options.style; - } - - if (options.loadParams) { - this.loadParams = options.loadParams; - } - } - - // console.log("Creating formatter for region: " + this.locale.region); - Utils.loadData({ - name: "address.json", - object: "AddressFmt", - locale: this.locale, - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(info) { - if (!info || JSUtils.isEmpty(info)) { - // load the "unknown" locale instead - Utils.loadData({ - name: "address.json", - object: "AddressFmt", - locale: new Locale("XX"), - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(info) { - this.info = info; - this._init(); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - this.info = info; - this._init(); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } - }) - }); + this.sync = true; + this.styleName = 'default'; + this.loadParams = {}; + this.locale = new Locale(); + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + if (options.style) { + this.styleName = options.style; + } + + if (options.loadParams) { + this.loadParams = options.loadParams; + } + } + + // console.log("Creating formatter for region: " + this.locale.region); + Utils.loadData({ + name: "address.json", + object: "AddressFmt", + locale: this.locale, + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(info) { + if (!info || JSUtils.isEmpty(info)) { + // load the "unknown" locale instead + Utils.loadData({ + name: "address.json", + object: "AddressFmt", + locale: new Locale("XX"), + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(info) { + this.info = info; + this._init(); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + this.info = info; + this._init(); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } + }) + }); }; /** * @private */ AddressFmt.prototype._init = function () { - this.style = this.info && this.info.formats && this.info.formats[this.styleName]; - - // use generic default -- should not happen, but just in case... - this.style = this.style || (this.info && this.info.formats && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; + this.style = this.info && this.info.formats && this.info.formats[this.styleName]; + + // use generic default -- should not happen, but just in case... + this.style = this.style || (this.info && this.info.formats && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; }; /** - * This function formats a physical address (Address instance) for display. - * Whitespace is trimmed from the beginning and end of final resulting string, and - * multiple consecutive whitespace characters in the middle of the string are + * This function formats a physical address (Address instance) for display. + * Whitespace is trimmed from the beginning and end of final resulting string, and + * multiple consecutive whitespace characters in the middle of the string are * compressed down to 1 space character. - * + * * If the Address instance is for a locale that is different than the locale for this * formatter, then a hybrid address is produced. The country name is located in the * correct spot for the current formatter's locale, but the rest of the fields are * formatted according to the default style of the locale of the actual address. - * + * * Example: a mailing address in China, but formatted for the US might produce the words - * "People's Republic of China" in English at the last line of the address, and the + * "People's Republic of China" in English at the last line of the address, and the * Chinese-style address will appear in the first line of the address. In the US, the * country is on the last line, but in China the country is usually on the first line. * @@ -154,50 +154,50 @@ AddressFmt.prototype._init = function () { * @returns {string} Returns a string containing the formatted address */ AddressFmt.prototype.format = function (address) { - var ret, template, other, format; - - if (!address) { - return ""; - } - // console.log("formatting address: " + JSON.stringify(address)); - if (address.countryCode && - address.countryCode !== this.locale.region && - Locale._isRegionCode(this.locale.region) && - this.locale.region !== "XX") { - // we are formatting an address that is sent from this country to another country, - // so only the country should be in this locale, and the rest should be in the other - // locale - // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); - other = new AddressFmt({ - locale: new Locale(address.countryCode), - style: this.styleName - }); - return other.format(address); - } - - if (typeof(this.style) === 'object') { - format = this.style[address.format || "latin"]; - } else { - format = this.style; - } - - // console.log("Using format: " + format); - // make sure we have a blank string for any missing parts so that - // those template parts get blanked out - var params = { - country: address.country || "", - region: address.region || "", - locality: address.locality || "", - streetAddress: address.streetAddress || "", - postalCode: address.postalCode || "", - postOffice: address.postOffice || "" - }; - template = new IString(format); - ret = template.format(params); - ret = ret.replace(/[ \t]+/g, ' '); - ret = ret.replace("\n ", "\n"); - ret = ret.replace(" \n", "\n"); - return ret.replace(/\n+/g, '\n').trim(); + var ret, template, other, format; + + if (!address) { + return ""; + } + // console.log("formatting address: " + JSON.stringify(address)); + if (address.countryCode && + address.countryCode !== this.locale.region && + Locale._isRegionCode(this.locale.region) && + this.locale.region !== "XX") { + // we are formatting an address that is sent from this country to another country, + // so only the country should be in this locale, and the rest should be in the other + // locale + // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); + other = new AddressFmt({ + locale: new Locale(address.countryCode), + style: this.styleName + }); + return other.format(address); + } + + if (typeof(this.style) === 'object') { + format = this.style[address.format || "latin"]; + } else { + format = this.style; + } + + // console.log("Using format: " + format); + // make sure we have a blank string for any missing parts so that + // those template parts get blanked out + var params = { + country: address.country || "", + region: address.region || "", + locality: address.locality || "", + streetAddress: address.streetAddress || "", + postalCode: address.postalCode || "", + postOffice: address.postOffice || "" + }; + template = new IString(format); + ret = template.format(params); + ret = ret.replace(/[ \t]+/g, ' '); + ret = ret.replace("\n ", "\n"); + ret = ret.replace(" \n", "\n"); + return ret.replace(/\n+/g, '\n').trim(); }; @@ -214,7 +214,7 @@ function isAsianLocale(locale) { * Invert the properties and values, filtering out all the regions. Regions either * have values with numbers (eg. "150" for Europe), or they are on a short list of * known regions with actual ISO codes. - * + * * @private * @returns {Object} the inverted object */ @@ -249,7 +249,7 @@ function invertAndFilter(object) { * particular pattern or to a fixed list of possible values, then * the constraint rules are given in the "constraint" property.

* - * If an address component must conform to a particular pattern, + * If an address component must conform to a particular pattern, * the regular expression that matches that pattern * is returned in "constraint". Mostly, it is only the postal code * component that can be validated in this way.

@@ -258,7 +258,7 @@ function invertAndFilter(object) { * to a fixed list of values, then the constraint property will be * set to an array that lists those values. The constraint contains * an array of objects in the correct sorted order for the locale - * where each object contains an code property containing the ISO code, + * where each object contains an code property containing the ISO code, * and a name field to show in UI. * The ISO codes should not be shown to the user and are intended to * represent the values in code. The names are translated to the given @@ -316,28 +316,25 @@ function invertAndFilter(object) { * *

* @example Example of calling the getFormatInfo method - * - * // the AddressFmt should be created with the locale of the address you + * + * // the AddressFmt should be created with the locale of the address you * // would like the user to enter. For example, if you have a "country" * // selector, you would create a new AddressFmt instance each time the * // selector is changed. * new AddressFmt({ * locale: 'nl-NL', // for addresses in the Netherlands * onLoad: ilib.bind(this, function(fmt) { - * fmt.getAddressFormatInfo({ - * // The following is the locale of the UI you would like to see the labels - * // like "City" and "Postal Code" translated to. In this example, we - * // are showing an input form for Dutch addresses, but the labels are - * // written in US English. - * locale: "en-US", - * onLoad: ilib.bind(this, function(rows) { - * // iterate through the rows array and dynamically create the input - * // elements with the given labels - * }) - * }); + * // The following is the locale of the UI you would like to see the labels + * // like "City" and "Postal Code" translated to. In this example, we + * // are showing an input form for Dutch addresses, but the labels are + * // written in US English. + * fmt.getAddressFormatInfo("en-US", true, ilib.bind(this, function(rows) { + * // iterate through the rows array and dynamically create the input + * // elements with the given labels + * })); * }) * }); - * + * * @param {Locale|string=} locale the locale to translate the labels * to. If not given, the locale of the formatter will be used. * @param {boolean=} sync true if this method should load the data @@ -370,7 +367,7 @@ AddressFmt.prototype.getFormatInfo = function(locale, sync, callback) { locale: loc, name: "addressres", sync: this.sync, - loadParams: this.loadParams, + loadParams: this.loadParams, onLoad: ilib.bind(this, function (rb) { var type, format, fields = this.info.fields; if (this.info.multiformat) { @@ -414,7 +411,7 @@ AddressFmt.prototype.getFormatInfo = function(locale, sync, callback) { return obj; })); })); - + if (callback && typeof(callback) === "function") { callback(info); } diff --git a/js/lib/AlphabeticIndex.js b/js/lib/AlphabeticIndex.js index 12ae8b375c..092d02e53e 100644 --- a/js/lib/AlphabeticIndex.js +++ b/js/lib/AlphabeticIndex.js @@ -19,7 +19,7 @@ // !data nfc nfkd -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); var CType = require("./CType.js"); @@ -40,7 +40,7 @@ var NormString = require("./NormString.js"); * list, organized by the first letter of the family name.

* * Example in English:

- * + * * Buckets: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z #

* *

diff --git a/js/lib/AreaUnit.js b/js/lib/AreaUnit.js
index 2a0f880620..856a566496 100644
--- a/js/lib/AreaUnit.js
+++ b/js/lib/AreaUnit.js
@@ -49,16 +49,16 @@ AreaUnit.prototype.parent = Measurement;
 AreaUnit.prototype.constructor = AreaUnit;
 
 AreaUnit.ratios = {
-    /*               index		square cm,		square meter,   hectare,   	square km, 	, square inch 	square foot, 		square yard, 	  	  	acre,			    square mile			        */
-    "square-centimeter":[1,   	1,				0.0001,			1e-8,	    1e-10,        0.15500031,	0.00107639104,		0.000119599005,			2.47105381e-8,		3.86102159e-11 		],
-    "square-meter": 	[2,   	10000,			1,              1e-4,       1e-6,         1550,    	 	10.7639,    	  	1.19599,   				0.000247105,		3.861e-7     	    ],
-    "hectare":      	[3,	 	100000000,  	10000,          1,          0.01,         1.55e+7, 	  	107639,     	 	11959.9,   				2.47105	,			0.00386102    	    ],
-    "square-kilometer": [4,	  	10000000000, 	1e+6,          	100,        1,	          1.55e+9, 	  	1.076e+7,   	 	1.196e+6,  				247.105 ,   		0.386102     	    ],
-    "square-inch":  	[5,	  	6.4516,			0.00064516,     6.4516e-8,  6.4516e-10,   1,			0.0069444444444444, 0.0007716051, 			1.5942e-7,			2.491e-10    	    ],
-    "square-foot":  	[6,		929.0304,		0.092903,       9.2903e-6,  9.2903e-8,    144,			1,          	  	0.111111,  				2.2957e-5,			3.587e-8    		],
-    "square-yard":  	[7,		8361.2736,		0.836127,       8.3613e-5,  8.3613e-7,    1296,    	  	9,          	  	1,         				0.000206612,		3.2283e-7    	    ],
-    "acre":         	[8,		40468564.2,		4046.86,        0.404686,   0.00404686,   6.273e+6,	  	43560,      	  	4840,      				1,		    		0.0015625    	    ],
-    "square-mile":  	[9,	   	2.58998811e+10,	2.59e+6,        258.999,    2.58999,      4.014e+9,	 	2.788e+7,   	  	3.098e+6,  				640,     			1   	     		]
+    /*               index        square cm,        square meter,   hectare,       square km,     , square inch     square foot,         square yard,                 acre,                square mile                    */
+    "square-centimeter":[1,       1,                0.0001,            1e-8,        1e-10,        0.15500031,    0.00107639104,        0.000119599005,            2.47105381e-8,        3.86102159e-11         ],
+    "square-meter":     [2,       10000,            1,              1e-4,       1e-6,         1550,             10.7639,              1.19599,                   0.000247105,        3.861e-7             ],
+    "hectare":          [3,         100000000,      10000,          1,          0.01,         1.55e+7,           107639,              11959.9,                   2.47105    ,            0.00386102            ],
+    "square-kilometer": [4,          10000000000,     1e+6,              100,        1,              1.55e+9,           1.076e+7,            1.196e+6,                  247.105 ,           0.386102             ],
+    "square-inch":      [5,          6.4516,            0.00064516,     6.4516e-8,  6.4516e-10,   1,            0.0069444444444444, 0.0007716051,             1.5942e-7,            2.491e-10            ],
+    "square-foot":      [6,        929.0304,        0.092903,       9.2903e-6,  9.2903e-8,    144,            1,                    0.111111,                  2.2957e-5,            3.587e-8            ],
+    "square-yard":      [7,        8361.2736,        0.836127,       8.3613e-5,  8.3613e-7,    1296,              9,                    1,                         0.000206612,        3.2283e-7            ],
+    "acre":             [8,        40468564.2,        4046.86,        0.404686,   0.00404686,   6.273e+6,          43560,                4840,                      1,                    0.0015625            ],
+    "square-mile":      [9,           2.58998811e+10,    2.59e+6,        258.999,    2.58999,      4.014e+9,         2.788e+7,             3.098e+6,                  640,                 1                    ]
 }
 
 /**
@@ -79,7 +79,7 @@ AreaUnit.prototype.getMeasure = function() {
 
 /**
  * Return a new instance of this type of measurement.
- * 
+ *
  * @param {Object} params parameters to the constructor
  * @return {Measurement} a measurement subclass instance
  */
@@ -174,12 +174,12 @@ AreaUnit.aliases = {
 AreaUnit.convert = function(to, from, area) {
     from = Measurement.getUnitIdCaseInsensitive(AreaUnit, from) || from;
     to = Measurement.getUnitIdCaseInsensitive(AreaUnit, to) || to;
-	var fromRow = AreaUnit.ratios[from];
-	var toRow = AreaUnit.ratios[to];
-	if (typeof(from) === 'undefined' || typeof(to) === 'undefined') {
-		return undefined;
-	}
-	return area* fromRow[toRow[0]];
+    var fromRow = AreaUnit.ratios[from];
+    var toRow = AreaUnit.ratios[to];
+    if (typeof(from) === 'undefined' || typeof(to) === 'undefined') {
+        return undefined;
+    }
+    return area* fromRow[toRow[0]];
 };
 
 /**
diff --git a/js/lib/Astro.js b/js/lib/Astro.js
index 69d913b2cc..a87c4094a0 100644
--- a/js/lib/Astro.js
+++ b/js/lib/Astro.js
@@ -1,6 +1,6 @@
 /*
  * Astro.js - Static functions to support astronomical calculations
- * 
+ *
  * Copyright © 2014-2015, 2018, JEDLSoft
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,12 +20,12 @@
 // !data astro
 
 /*
- * These routines were derived from a public domain set of JavaScript 
- * functions for positional astronomy by John Walker of Fourmilab, 
+ * These routines were derived from a public domain set of JavaScript
+ * functions for positional astronomy by John Walker of Fourmilab,
  * September 1999.
  */
 
-var ilib = require("./ilib.js");
+var ilib = require("../index");
 var Utils = require("./Utils.js");
 var SearchUtils = require("./SearchUtils.js");
 
@@ -37,101 +37,101 @@ var Astro = {};
 
 /**
  * Load in all the data needed for astrological calculations.
- * 
+ *
  * @private
  * @param {boolean} sync
  * @param {*} loadParams
  * @param {function(*)|undefined} callback
  */
 Astro.initAstro = function(sync, loadParams, callback) {
-	if (!ilib.data.astro) {
-		Utils.loadData({
-			object: "Astro",
-			name: "astro.json", // countries in their own language 
-			locale: "-", // only need to load the root file 
-			nonLocale: true,
-			sync: sync, 
-			loadParams: loadParams, 
-			callback: ilib.bind(this, function(astroData) {
-				/** 
-				 * @type {{
-				 *  	_EquinoxpTerms:Array., 
-				 *  	_JDE0tab1000:Array., 
-				 *  	_JDE0tab2000:Array., 
-				 *  	_deltaTtab:Array.,
-				 *  	_oterms:Array.,
-				 *  	_nutArgMult:Array., 
-				 *  	_nutArgCoeff:Array., 
-				 *  	_nutCoeffA:Array.,
-				 *  	_nutCoeffB:Array.,
-				 *  	_coeff19th:Array.,
-				 *  	_coeff18th:Array.,
-				 *  	_solarLongCoeff:Array., 
-				 *  	_solarLongMultipliers:Array., 
-				 *  	_solarLongAddends:Array., 
-				 *  	_meanMoonCoeff:Array.,
-				 *  	_elongationCoeff:Array.,
-				 *  	_solarAnomalyCoeff:Array.,
-				 *  	_lunarAnomalyCoeff:Array.,
-				 *  	_moonFromNodeCoeff:Array.,
-				 *  	_eCoeff:Array.,
-				 *  	_lunarElongationLongCoeff:Array.,
-				 *  	_solarAnomalyLongCoeff:Array.,
-				 *  	_lunarAnomalyLongCoeff:Array.,
-				 *  	_moonFromNodeLongCoeff:Array.,
-				 *  	_sineCoeff:Array.,
-				 *  	_nmApproxCoeff:Array.,
-				 *  	_nmCapECoeff:Array.,
-				 *  	_nmSolarAnomalyCoeff:Array.,
-				 *  	_nmLunarAnomalyCoeff:Array.,
-				 *  	_nmMoonArgumentCoeff:Array.,
-				 *  	_nmCapOmegaCoeff:Array.,
-				 *  	_nmEFactor:Array.,
-				 *  	_nmSolarCoeff:Array.,
-				 *  	_nmLunarCoeff:Array.,
-				 *  	_nmMoonCoeff:Array.,
-				 *  	_nmSineCoeff:Array.,
-				 *  	_nmAddConst:Array.,
-				 *  	_nmAddCoeff:Array.,
-				 *  	_nmAddFactor:Array.,
-				 *  	_nmExtra:Array.
-				 *  }}
-				 */ 	
-			 	ilib.data.astro = astroData;
-				if (callback && typeof(callback) === 'function') {
-					callback(astroData);
-				}
-			})
-		});
-	} else {
-		if (callback && typeof(callback) === 'function') {
-			callback(ilib.data.astro);
-		}
-	}
+    if (!ilib.data.astro) {
+        Utils.loadData({
+            object: "Astro",
+            name: "astro.json", // countries in their own language
+            locale: "-", // only need to load the root file
+            nonLocale: true,
+            sync: sync,
+            loadParams: loadParams,
+            callback: ilib.bind(this, function(astroData) {
+                /**
+                 * @type {{
+                 *      _EquinoxpTerms:Array.,
+                 *      _JDE0tab1000:Array.,
+                 *      _JDE0tab2000:Array.,
+                 *      _deltaTtab:Array.,
+                 *      _oterms:Array.,
+                 *      _nutArgMult:Array.,
+                 *      _nutArgCoeff:Array.,
+                 *      _nutCoeffA:Array.,
+                 *      _nutCoeffB:Array.,
+                 *      _coeff19th:Array.,
+                 *      _coeff18th:Array.,
+                 *      _solarLongCoeff:Array.,
+                 *      _solarLongMultipliers:Array.,
+                 *      _solarLongAddends:Array.,
+                 *      _meanMoonCoeff:Array.,
+                 *      _elongationCoeff:Array.,
+                 *      _solarAnomalyCoeff:Array.,
+                 *      _lunarAnomalyCoeff:Array.,
+                 *      _moonFromNodeCoeff:Array.,
+                 *      _eCoeff:Array.,
+                 *      _lunarElongationLongCoeff:Array.,
+                 *      _solarAnomalyLongCoeff:Array.,
+                 *      _lunarAnomalyLongCoeff:Array.,
+                 *      _moonFromNodeLongCoeff:Array.,
+                 *      _sineCoeff:Array.,
+                 *      _nmApproxCoeff:Array.,
+                 *      _nmCapECoeff:Array.,
+                 *      _nmSolarAnomalyCoeff:Array.,
+                 *      _nmLunarAnomalyCoeff:Array.,
+                 *      _nmMoonArgumentCoeff:Array.,
+                 *      _nmCapOmegaCoeff:Array.,
+                 *      _nmEFactor:Array.,
+                 *      _nmSolarCoeff:Array.,
+                 *      _nmLunarCoeff:Array.,
+                 *      _nmMoonCoeff:Array.,
+                 *      _nmSineCoeff:Array.,
+                 *      _nmAddConst:Array.,
+                 *      _nmAddCoeff:Array.,
+                 *      _nmAddFactor:Array.,
+                 *      _nmExtra:Array.
+                 *  }}
+                 */
+                 ilib.data.astro = astroData;
+                if (callback && typeof(callback) === 'function') {
+                    callback(astroData);
+                }
+            })
+        });
+    } else {
+        if (callback && typeof(callback) === 'function') {
+            callback(ilib.data.astro);
+        }
+    }
 };
 
 /**
  * Convert degrees to radians.
- * 
+ *
  * @static
  * @protected
  * @param {number} d angle in degrees
- * @return {number} angle in radians 
+ * @return {number} angle in radians
  */
 Astro._dtr = function(d) {
-	return (d * Math.PI) / 180.0;
+    return (d * Math.PI) / 180.0;
 };
 
 /**
  * Convert radians to degrees.
- * 
+ *
  * @static
  * @protected
  * @param {number} r angle in radians
- * @return {number} angle in degrees 
+ * @return {number} angle in degrees
  */
 Astro._rtd = function(r) {
-	return (r * 180.0) / Math.PI;
+    return (r * 180.0) / Math.PI;
 };
 
 /**
@@ -140,9 +140,9 @@ Astro._rtd = function(r) {
  * @protected
  * @param {number} d angle in degrees
  * @return {number} cosine of the angle.
- */  
+ */
 Astro._dcos = function(d) {
-	return Math.cos(Astro._dtr(d));
+    return Math.cos(Astro._dtr(d));
 };
 
 /**
@@ -151,9 +151,9 @@ Astro._dcos = function(d) {
  * @protected
  * @param {number} d angle in degrees
  * @return {number} sine of the angle.
- */  
+ */
 Astro._dsin = function(d) {
-	return Math.sin(Astro._dtr(d));
+    return Math.sin(Astro._dtr(d));
 };
 
 /**
@@ -162,97 +162,97 @@ Astro._dsin = function(d) {
  * @protected
  * @param {number} d angle in degrees
  * @return {number} tan of the angle.
- */  
+ */
 Astro._dtan = function(d) {
-	return Math.tan(Astro._dtr(d));
+    return Math.tan(Astro._dtr(d));
 };
 
 /**
  * Range reduce angle in degrees.
- * 
+ *
  * @static
  * @param {number} a angle to reduce
- * @return {number} the reduced angle  
+ * @return {number} the reduced angle
  */
 Astro._fixangle = function(a) {
-	return a - 360.0 * (Math.floor(a / 360.0));
+    return a - 360.0 * (Math.floor(a / 360.0));
 };
 
 /**
  * Range reduce angle in radians.
- * 
+ *
  * @static
  * @protected
  * @param {number} a angle to reduce
- * @return {number} the reduced angle  
+ * @return {number} the reduced angle
  */
 Astro._fixangr = function(a) {
-	return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI)));
+    return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI)));
 };
 
 /**
- * Determine the Julian Ephemeris Day of an equinox or solstice.  The "which" 
+ * Determine the Julian Ephemeris Day of an equinox or solstice.  The "which"
  * argument selects the item to be computed:
- * 
+ *
  * 
    *
  • 0 March equinox *
  • 1 June solstice *
  • 2 September equinox *
  • 3 December solstice *
- * + * * @static * @protected * @param {number} year Gregorian year to calculate for * @param {number} which Which equinox or solstice to calculate */ Astro._equinox = function(year, which) { - var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; + var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; - /* Initialize terms for mean equinox and solstices. We - have two sets: one for years prior to 1000 and a second - for subsequent years. */ + /* Initialize terms for mean equinox and solstices. We + have two sets: one for years prior to 1000 and a second + for subsequent years. */ - if (year < 1000) { - JDE0tab = ilib.data.astro._JDE0tab1000; - Y = year / 1000; - } else { - JDE0tab = ilib.data.astro._JDE0tab2000; - Y = (year - 2000) / 1000; - } + if (year < 1000) { + JDE0tab = ilib.data.astro._JDE0tab1000; + Y = year / 1000; + } else { + JDE0tab = ilib.data.astro._JDE0tab2000; + Y = (year - 2000) / 1000; + } - JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) - + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) - + (JDE0tab[which][4] * Y * Y * Y * Y); + JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) + + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) + + (JDE0tab[which][4] * Y * Y * Y * Y); - //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; + //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; - T = (JDE0 - 2451545.0) / 36525; - //document.debug.log.value += "T = " + T + "\n"; - W = (35999.373 * T) - 2.47; - //document.debug.log.value += "W = " + W + "\n"; - deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); - //document.debug.log.value += "deltaL = " + deltaL + "\n"; + T = (JDE0 - 2451545.0) / 36525; + //document.debug.log.value += "T = " + T + "\n"; + W = (35999.373 * T) - 2.47; + //document.debug.log.value += "W = " + W + "\n"; + deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); + //document.debug.log.value += "deltaL = " + deltaL + "\n"; - // Sum the periodic terms for time T + // Sum the periodic terms for time T - S = 0; - j = 0; - for (i = 0; i < 24; i++) { - S += ilib.data.astro._EquinoxpTerms[j] - * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); - j += 3; - } + S = 0; + j = 0; + for (i = 0; i < 24; i++) { + S += ilib.data.astro._EquinoxpTerms[j] + * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); + j += 3; + } - //document.debug.log.value += "S = " + S + "\n"; - //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; + //document.debug.log.value += "S = " + S + "\n"; + //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; - JDE = JDE0 + ((S * 0.00001) / deltaL); + JDE = JDE0 + ((S * 0.00001) / deltaL); - return JDE; + return JDE; }; -/* +/* * The table of observed Delta T values at the beginning of * years from 1620 through 2014 as found in astro.json is taken from * http://www.staff.science.uu.nl/~gent0113/deltat/deltat.htm @@ -260,33 +260,33 @@ Astro._equinox = function(year, which) { * ftp://maia.usno.navy.mil/ser7/deltat.data */ -/** +/** * Determine the difference, in seconds, between dynamical time and universal time. - * + * * @static * @protected * @param {number} year to calculate the difference for - * @return {number} difference in seconds between dynamical time and universal time + * @return {number} difference in seconds between dynamical time and universal time */ Astro._deltat = function (year) { - var dt, f, i, t; - - if ((year >= 1620) && (year <= 2014)) { - i = Math.floor(year - 1620); - f = (year - 1620) - i; /* Fractional part of year */ - dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); - } else { - t = (year - 2000) / 100; - if (year < 948) { - dt = 2177 + (497 * t) + (44.1 * t * t); - } else { - dt = 102 + (102 * t) + (25.3 * t * t); - if ((year > 2000) && (year < 2100)) { - dt += 0.37 * (year - 2100); - } - } - } - return dt; + var dt, f, i, t; + + if ((year >= 1620) && (year <= 2014)) { + i = Math.floor(year - 1620); + f = (year - 1620) - i; /* Fractional part of year */ + dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); + } else { + t = (year - 2000) / 100; + if (year < 948) { + dt = 2177 + (497 * t) + (44.1 * t * t); + } else { + dt = 102 + (102 * t) + (25.3 * t * t); + if ((year > 2000) && (year < 2100)) { + dt += 0.37 * (year - 2100); + } + } + } + return dt; }; /** @@ -300,26 +300,26 @@ Astro._deltat = function (year) { * range in which this fit is valid (deep time) we * simply return the J2000 value of the obliquity, which * happens to be almost precisely the mean. - * + * * @static * @protected * @param {number} jd Julian Day to calculate the obliquity for * @return {number} the obliquity */ Astro._obliqeq = function (jd) { - var eps, u, v, i; + var eps, u, v, i; - v = u = (jd - 2451545.0) / 3652500.0; + v = u = (jd - 2451545.0) / 3652500.0; - eps = 23 + (26 / 60.0) + (21.448 / 3600.0); + eps = 23 + (26 / 60.0) + (21.448 / 3600.0); - if (Math.abs(u) < 1.0) { - for (i = 0; i < 10; i++) { - eps += (ilib.data.astro._oterms[i] / 3600.0) * v; - v *= u; - } - } - return eps; + if (Math.abs(u) < 1.0) { + for (i = 0; i < 10; i++) { + eps += (ilib.data.astro._oterms[i] / 3600.0) * v; + v *= u; + } + } + return eps; }; /** @@ -333,167 +333,167 @@ Astro._obliqeq = function (jd) { * values */ Astro._sunpos = function(jd) { - var ret = {}, - T, T2, T3, Omega, epsilon, epsilon0; - - T = (jd - 2451545.0) / 36525.0; - //document.debug.log.value += "Sunpos. T = " + T + "\n"; - T2 = T * T; - T3 = T * T2; - ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); - //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; - ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); - //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; - ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; - //document.debug.log.value += "e = " + e + "\n"; - ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) - + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) - + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); - //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; - ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; - //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; - //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; - //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; - // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); - //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; - Omega = 125.04 - (1934.136 * T); - //document.debug.log.value += "Omega = " + Omega + "\n"; - ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); - //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; - epsilon0 = Astro._obliqeq(jd); - //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; - epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); - //document.debug.log.value += "epsilon = " + epsilon + "\n"; - //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); - //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; - // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); - ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; - ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); - ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); - //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; - //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); - //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; - - // Angular quantities are expressed in decimal degrees - return ret; -}; - -/** - * Calculate the nutation in longitude, deltaPsi, and obliquity, + var ret = {}, + T, T2, T3, Omega, epsilon, epsilon0; + + T = (jd - 2451545.0) / 36525.0; + //document.debug.log.value += "Sunpos. T = " + T + "\n"; + T2 = T * T; + T3 = T * T2; + ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); + //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; + ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); + //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; + ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; + //document.debug.log.value += "e = " + e + "\n"; + ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) + + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) + + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); + //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; + ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; + //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; + //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; + //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; + // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); + //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; + Omega = 125.04 - (1934.136 * T); + //document.debug.log.value += "Omega = " + Omega + "\n"; + ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); + //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; + epsilon0 = Astro._obliqeq(jd); + //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; + epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); + //document.debug.log.value += "epsilon = " + epsilon + "\n"; + //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); + //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; + // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); + ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; + ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); + ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); + //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; + //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); + //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; + + // Angular quantities are expressed in decimal degrees + return ret; +}; + +/** + * Calculate the nutation in longitude, deltaPsi, and obliquity, * deltaEpsilon for a given Julian date jd. Results are returned as an object * giving deltaPsi and deltaEpsilon in degrees. - * + * * @static * @protected * @param {number} jd calculate the nutation of this Julian Day * @return {Object} the deltaPsi and deltaEpsilon of the nutation */ Astro._nutation = function(jd) { - var i, j, - t = (jd - 2451545.0) / 36525.0, - t2, t3, to10, - ta = [], - dp = 0, - de = 0, - ang, - ret = {}; - - t3 = t * (t2 = t * t); - - /* - * Calculate angles. The correspondence between the elements of our array - * and the terms cited in Meeus are: - * - * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega - * - */ - - ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); - ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); - ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); - ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); - ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); - - /* - * Range reduce the angles in case the sine and cosine functions don't do it - * as accurately or quickly. - */ - - for (i = 0; i < 5; i++) { - ta[i] = Astro._fixangr(ta[i]); - } - - to10 = t / 10.0; - for (i = 0; i < 63; i++) { - ang = 0; - for (j = 0; j < 5; j++) { - if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { - ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; - } - } - dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); - de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); - } - - /* - * Return the result, converting from ten thousandths of arc seconds to - * radians in the process. - */ - - ret.deltaPsi = dp / (3600.0 * 10000.0); - ret.deltaEpsilon = de / (3600.0 * 10000.0); - - return ret; + var i, j, + t = (jd - 2451545.0) / 36525.0, + t2, t3, to10, + ta = [], + dp = 0, + de = 0, + ang, + ret = {}; + + t3 = t * (t2 = t * t); + + /* + * Calculate angles. The correspondence between the elements of our array + * and the terms cited in Meeus are: + * + * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega + * + */ + + ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); + ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); + ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); + ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); + ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); + + /* + * Range reduce the angles in case the sine and cosine functions don't do it + * as accurately or quickly. + */ + + for (i = 0; i < 5; i++) { + ta[i] = Astro._fixangr(ta[i]); + } + + to10 = t / 10.0; + for (i = 0; i < 63; i++) { + ang = 0; + for (j = 0; j < 5; j++) { + if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { + ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; + } + } + dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); + de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); + } + + /* + * Return the result, converting from ten thousandths of arc seconds to + * radians in the process. + */ + + ret.deltaPsi = dp / (3600.0 * 10000.0); + ret.deltaEpsilon = de / (3600.0 * 10000.0); + + return ret; }; /** * Returns the equation of time as a fraction of a day. - * + * * @static * @protected * @param {number} jd the Julian Day of the day to calculate for - * @return {number} the equation of time for the given day + * @return {number} the equation of time for the given day */ Astro._equationOfTime = function(jd) { - var alpha, deltaPsi, E, epsilon, L0, tau, pos; - - // 2451545.0 is the Julian day of J2000 epoch - // 365250.0 is the number of days in a Julian millenium - tau = (jd - 2451545.0) / 365250.0; - //console.log("equationOfTime. tau = " + tau); - L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) - + ((tau * tau * tau) / 49931) - + (-((tau * tau * tau * tau) / 15300)) - + (-((tau * tau * tau * tau * tau) / 2000000)); - //console.log("L0 = " + L0); - L0 = Astro._fixangle(L0); - //console.log("L0 = " + L0); - pos = Astro._sunpos(jd); - alpha = pos.apparentRightAscension; - //console.log("alpha = " + alpha); - var nut = Astro._nutation(jd); - deltaPsi = nut.deltaPsi; - //console.log("deltaPsi = " + deltaPsi); - epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; - //console.log("epsilon = " + epsilon); - //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); - //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); - //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); - - E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); - // if alpha and L0 are in different quadrants, then renormalize - // so that the difference between them is in the right range - if (E > 180) { - E -= 360; - } - //console.log("E = " + E); - // E = E - 20.0 * (Math.floor(E / 20.0)); - E = E * 4; - //console.log("Efixed = " + E); - E = E / (24 * 60); - //console.log("Eday = " + E); - - return E; + var alpha, deltaPsi, E, epsilon, L0, tau, pos; + + // 2451545.0 is the Julian day of J2000 epoch + // 365250.0 is the number of days in a Julian millenium + tau = (jd - 2451545.0) / 365250.0; + //console.log("equationOfTime. tau = " + tau); + L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) + + ((tau * tau * tau) / 49931) + + (-((tau * tau * tau * tau) / 15300)) + + (-((tau * tau * tau * tau * tau) / 2000000)); + //console.log("L0 = " + L0); + L0 = Astro._fixangle(L0); + //console.log("L0 = " + L0); + pos = Astro._sunpos(jd); + alpha = pos.apparentRightAscension; + //console.log("alpha = " + alpha); + var nut = Astro._nutation(jd); + deltaPsi = nut.deltaPsi; + //console.log("deltaPsi = " + deltaPsi); + epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; + //console.log("epsilon = " + epsilon); + //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); + //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); + //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); + + E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); + // if alpha and L0 are in different quadrants, then renormalize + // so that the difference between them is in the right range + if (E > 180) { + E -= 360; + } + //console.log("E = " + E); + // E = E - 20.0 * (Math.floor(E / 20.0)); + E = E * 4; + //console.log("Efixed = " + E); + E = E / (24 * 60); + //console.log("Eday = " + E); + + return E; }; /** @@ -501,41 +501,41 @@ Astro._equationOfTime = function(jd) { * @static */ Astro._poly = function(x, coefficients) { - var result = coefficients[0]; - var xpow = x; - for (var i = 1; i < coefficients.length; i++) { - result += coefficients[i] * xpow; - xpow *= x; - } - return result; + var result = coefficients[0]; + var xpow = x; + for (var i = 1; i < coefficients.length; i++) { + result += coefficients[i] * xpow; + xpow *= x; + } + return result; }; /** * Calculate the UTC RD from the local RD given "zone" number of minutes * worth of offset. - * + * * @static * @protected * @param {number} local RD of the locale time, given in any calendar - * @param {number} zone number of minutes of offset from UTC for the time zone + * @param {number} zone number of minutes of offset from UTC for the time zone * @return {number} the UTC equivalent of the local RD */ Astro._universalFromLocal = function(local, zone) { - return local - zone / 1440; + return local - zone / 1440; }; /** * Calculate the local RD from the UTC RD given "zone" number of minutes * worth of offset. - * + * * @static * @protected * @param {number} local RD of the locale time, given in any calendar - * @param {number} zone number of minutes of offset from UTC for the time zone + * @param {number} zone number of minutes of offset from UTC for the time zone * @return {number} the UTC equivalent of the local RD */ Astro._localFromUniversal = function(local, zone) { - return local + zone / 1440; + return local + zone / 1440; }; /** @@ -545,7 +545,7 @@ Astro._localFromUniversal = function(local, zone) { * @return {number} the aberration */ Astro._aberration = function(c) { - return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; + return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; }; /** @@ -562,10 +562,10 @@ ilib.data.astro._nutCoeffB q= [201.11, 72001.5377, 0.00057]; * @return {number} the nutation for the given julian century in radians */ Astro._nutation2 = function(c) { - var a = Astro._poly(c, ilib.data.astro._nutCoeffA); - var b = Astro._poly(c, ilib.data.astro._nutCoeffB); - // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); - return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); + var a = Astro._poly(c, ilib.data.astro._nutCoeffA); + var b = Astro._poly(c, ilib.data.astro._nutCoeffB); + // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); + return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); }; /** @@ -573,44 +573,44 @@ Astro._nutation2 = function(c) { * @private */ Astro._ephemerisCorrection = function(jd) { - var year = GregorianDate._calcYear(jd - 1721424.5); - - if (1988 <= year && year <= 2019) { - return (year - 1933) / 86400; - } - - if (1800 <= year && year <= 1987) { - var jul1 = new GregRataDie({ - year: year, - month: 7, - day: 1, - hour: 0, - minute: 0, - second: 0 - }); - // 693596 is the rd of Jan 1, 1900 - var theta = (jul1.getRataDie() - 693596) / 36525; - return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); - } - - if (1620 <= year && year <= 1799) { - year -= 1600; - return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; - } - - // 660724 is the rd of Jan 1, 1810 - var jan1 = new GregRataDie({ - year: year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0 - }); - // var x = 0.5 + (jan1.getRataDie() - 660724); - var x = 0.5 + (jan1.getRataDie() - 660724); - - return ((x * x / 41048480) - 15) / 86400; + var year = GregorianDate._calcYear(jd - 1721424.5); + + if (1988 <= year && year <= 2019) { + return (year - 1933) / 86400; + } + + if (1800 <= year && year <= 1987) { + var jul1 = new GregRataDie({ + year: year, + month: 7, + day: 1, + hour: 0, + minute: 0, + second: 0 + }); + // 693596 is the rd of Jan 1, 1900 + var theta = (jul1.getRataDie() - 693596) / 36525; + return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); + } + + if (1620 <= year && year <= 1799) { + year -= 1600; + return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; + } + + // 660724 is the rd of Jan 1, 1810 + var jan1 = new GregRataDie({ + year: year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0 + }); + // var x = 0.5 + (jan1.getRataDie() - 660724); + var x = 0.5 + (jan1.getRataDie() - 660724); + + return ((x * x / 41048480) - 15) / 86400; }; /** @@ -618,7 +618,7 @@ Astro._ephemerisCorrection = function(jd) { * @private */ Astro._ephemerisFromUniversal = function(jd) { - return jd + Astro._ephemerisCorrection(jd); + return jd + Astro._ephemerisCorrection(jd); }; /** @@ -626,7 +626,7 @@ Astro._ephemerisFromUniversal = function(jd) { * @private */ Astro._universalFromEphemeris = function(jd) { - return jd - Astro._ephemerisCorrection(jd); + return jd - Astro._ephemerisCorrection(jd); }; /** @@ -634,33 +634,33 @@ Astro._universalFromEphemeris = function(jd) { * @private */ Astro._julianCenturies = function(jd) { - // 2451545.0 is the Julian day of J2000 epoch - // 730119.5 is the Gregorian RD of J2000 epoch - // 36525.0 is the number of days in a Julian century - return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; + // 2451545.0 is the Julian day of J2000 epoch + // 730119.5 is the Gregorian RD of J2000 epoch + // 36525.0 is the number of days in a Julian century + return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; }; /** * Calculate the solar longitude - * + * * @static * @protected - * @param {number} jd julian day of the date to calculate the longitude for + * @param {number} jd julian day of the date to calculate the longitude for * @return {number} the solar longitude in degrees */ Astro._solarLongitude = function(jd) { - var c = Astro._julianCenturies(jd), - longitude = 0, - len = ilib.data.astro._solarLongCoeff.length; - - for (var i = 0; i < len; i++) { - longitude += ilib.data.astro._solarLongCoeff[i] * - Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); - } - longitude *= 5.729577951308232e-06; - longitude += 282.77718340000001 + 36000.769537439999 * c; - longitude += Astro._aberration(c) + Astro._nutation2(c); - return Astro._fixangle(longitude); + var c = Astro._julianCenturies(jd), + longitude = 0, + len = ilib.data.astro._solarLongCoeff.length; + + for (var i = 0; i < len; i++) { + longitude += ilib.data.astro._solarLongCoeff[i] * + Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); + } + longitude *= 5.729577951308232e-06; + longitude += 282.77718340000001 + 36000.769537439999 * c; + longitude += Astro._aberration(c) + Astro._nutation2(c); + return Astro._fixangle(longitude); }; /** @@ -670,29 +670,29 @@ Astro._solarLongitude = function(jd) { * @return {number} */ Astro._lunarLongitude = function (jd) { - var c = Astro._julianCenturies(jd), - meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), - elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), - solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), - lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), - moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), - e = Astro._poly(c, ilib.data.astro._eCoeff); - - var sum = 0; - for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { - var x = ilib.data.astro._solarAnomalyLongCoeff[i]; - - sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * - Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + - ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + - ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); - } - var longitude = sum / 1000000; - var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); - var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); - var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); - - return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); + var c = Astro._julianCenturies(jd), + meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), + elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), + solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), + lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), + moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), + e = Astro._poly(c, ilib.data.astro._eCoeff); + + var sum = 0; + for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { + var x = ilib.data.astro._solarAnomalyLongCoeff[i]; + + sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * + Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + + ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + + ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); + } + var longitude = sum / 1000000; + var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); + var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); + var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); + + return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); }; /** @@ -702,28 +702,28 @@ Astro._lunarLongitude = function (jd) { * @return {number} julian day of the n'th new moon */ Astro._newMoonTime = function(n) { - var k = n - 24724; - var c = k / 1236.8499999999999; - var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); - var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); - var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); - var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); - var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); - var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); - var correction = -0.00017 * Astro._dsin(capOmega); - for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { - correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * - Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + - ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + - ilib.data.astro._nmMoonCoeff[i] * moonArgument); - } - var additional = 0; - for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { - additional = additional + ilib.data.astro._nmAddFactor[i] * - Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); - } - var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); - return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); + var k = n - 24724; + var c = k / 1236.8499999999999; + var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); + var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); + var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); + var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); + var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); + var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); + var correction = -0.00017 * Astro._dsin(capOmega); + for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { + correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * + Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + + ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + + ilib.data.astro._nmMoonCoeff[i] * moonArgument); + } + var additional = 0; + for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { + additional = additional + ilib.data.astro._nmAddFactor[i] * + Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); + } + var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); + return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); }; /** @@ -733,9 +733,9 @@ Astro._newMoonTime = function(n) { * @return {number} */ Astro._lunarSolarAngle = function(jd) { - var lunar = Astro._lunarLongitude(jd); - var solar = Astro._solarLongitude(jd) - return Astro._fixangle(lunar - solar); + var lunar = Astro._lunarLongitude(jd); + var solar = Astro._solarLongitude(jd) + return Astro._fixangle(lunar - solar); }; /** @@ -745,18 +745,18 @@ Astro._lunarSolarAngle = function(jd) { * @return {number} */ Astro._newMoonBefore = function (jd) { - var phase = Astro._lunarSolarAngle(jd); - // 11.450086114414322 is the julian day of the 0th full moon - // 29.530588853000001 is the average length of a month - var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; - var current, last; - current = last = Astro._newMoonTime(guess); - while (current < jd) { - guess++; - last = current; - current = Astro._newMoonTime(guess); - } - return last; + var phase = Astro._lunarSolarAngle(jd); + // 11.450086114414322 is the julian day of the 0th full moon + // 29.530588853000001 is the average length of a month + var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; + var current, last; + current = last = Astro._newMoonTime(guess); + while (current < jd) { + guess++; + last = current; + current = Astro._newMoonTime(guess); + } + return last; }; /** @@ -766,58 +766,58 @@ Astro._newMoonBefore = function (jd) { * @return {number} */ Astro._newMoonAtOrAfter = function (jd) { - var phase = Astro._lunarSolarAngle(jd); - // 11.450086114414322 is the julian day of the 0th full moon - // 29.530588853000001 is the average length of a month - var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); - var current; - while ((current = Astro._newMoonTime(guess)) < jd) { - guess++; - } - return current; + var phase = Astro._lunarSolarAngle(jd); + // 11.450086114414322 is the julian day of the 0th full moon + // 29.530588853000001 is the average length of a month + var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); + var current; + while ((current = Astro._newMoonTime(guess)) < jd) { + guess++; + } + return current; }; /** * @static * @protected * @param {number} jd JD to calculate from - * @param {number} longitude longitude to seek - * @returns {number} the JD of the next time that the solar longitude + * @param {number} longitude longitude to seek + * @returns {number} the JD of the next time that the solar longitude * is a multiple of the given longitude */ Astro._nextSolarLongitude = function(jd, longitude) { - var rate = 365.242189 / 360.0; - var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); - var start = Math.max(jd, tau - 5.0); - var end = tau + 5.0; - - return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { - return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); - }); + var rate = 365.242189 / 360.0; + var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); + var start = Math.max(jd, tau - 5.0); + var end = tau + 5.0; + + return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { + return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); + }); }; /** * Floor the julian day to midnight of the current julian day. - * + * * @static * @protected * @param {number} jd the julian to round * @return {number} the jd floored to the midnight of the julian day */ Astro._floorToJD = function(jd) { - return Math.floor(jd - 0.5) + 0.5; + return Math.floor(jd - 0.5) + 0.5; }; /** * Floor the julian day to midnight of the current julian day. - * + * * @static * @protected * @param {number} jd the julian to round * @return {number} the jd floored to the midnight of the julian day */ Astro._ceilToJD = function(jd) { - return Math.ceil(jd + 0.5) - 0.5; + return Math.ceil(jd + 0.5) - 0.5; }; module.exports = Astro; diff --git a/js/lib/AsyncNodeLoader.js b/js/lib/AsyncNodeLoader.js index 3398a3f3f6..29a275daaa 100644 --- a/js/lib/AsyncNodeLoader.js +++ b/js/lib/AsyncNodeLoader.js @@ -1,7 +1,7 @@ /* * AsyncAsyncNodeLoader.js - Loader implementation for nodejs with asynchronous calls. * Mostly this is used for testing the async calls - * + * * Copyright © 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ /** * @class * Implementation of async Loader for nodejs. - * + * * @constructor * @private */ @@ -47,7 +47,9 @@ module.exports = function (ilib) { //console.log("module.filename is " + module.filename + "\n"); //console.log("base is defined as " + this.base + "\n"); - this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first + // this.includePath.push(path.join(this.root, "resources")); + this._exists(this.root, "resources"); // always check the application's resources dir first + this._exists(path.join(this.root, "locale"), "localeinfo.json"); // then a standard locale dir of a built version of ilib from npm this._exists(path.join(this.base, "locale"), "localeinfo.json"); @@ -71,8 +73,8 @@ module.exports = function (ilib) { var text; //console.log("AsyncNodeLoader._loadFile: loading " + pathname + (sync ? " sync" : " async")); try { - // on node, just secret load everything synchronously, even when asynchronous - // load is requested, or else you will get crazy results where files are not read + // on node, just secret load everything synchronously, even when asynchronous + // load is requested, or else you will get crazy results where files are not read // until a long time later when the run queue is free fs.readFile(pathname, "utf-8", function(err, text) { if (typeof(cb) === 'function') { diff --git a/js/lib/CType.js b/js/lib/CType.js index f820c04991..46c6f5ebef 100644 --- a/js/lib/CType.js +++ b/js/lib/CType.js @@ -1,6 +1,6 @@ /* * CType.js - Character type definitions - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,14 +19,14 @@ // !data ctype -var ilib = require("./ilib.js"); +var ilib = require("../index"); var SearchUtils = require("./SearchUtils.js"); var Utils = require("./Utils.js"); var IString = require("./IString.js"); /** * Provides a set of static routines that return information about characters. - * These routines emulate the C-library ctype functions. The characters must be + * These routines emulate the C-library ctype functions. The characters must be * encoded in utf-16, as no other charsets are currently supported. Only the first * character of the given string is tested. * @namespace @@ -36,9 +36,9 @@ var CType = {}; /** * Actual implementation for withinRange. Searches the given object for ranges. - * The range names are taken from the Unicode range names in + * The range names are taken from the Unicode range names in * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt - * + * *
    *
  • Cn - Unassigned *
  • Lu - Uppercase_Letter @@ -71,7 +71,7 @@ var CType = {}; *
  • Pi - Initial_Punctuation *
  • Pf - Final_Punctuation *
- * + * * @protected * @param {number} num code point of the character to examine * @param {string} rangeName the name of the range to check @@ -80,35 +80,35 @@ var CType = {}; * range */ CType._inRange = function(num, rangeName, obj) { - var range; - if (num < 0 || !rangeName || !obj) { - return false; - } - - range = obj[rangeName]; - if (!range) { - return false; - } - - var compare = function(singlerange, target) { - if (singlerange.length === 1) { - return singlerange[0] - target; - } else { - return target < singlerange[0] ? singlerange[0] - target : - (target > singlerange[1] ? singlerange[1] - target : 0); - } - }; - var result = SearchUtils.bsearch(num, range, compare); - return result < range.length && compare(range[result], num) === 0; + var range; + if (num < 0 || !rangeName || !obj) { + return false; + } + + range = obj[rangeName]; + if (!range) { + return false; + } + + var compare = function(singlerange, target) { + if (singlerange.length === 1) { + return singlerange[0] - target; + } else { + return target < singlerange[0] ? singlerange[0] - target : + (target > singlerange[1] ? singlerange[1] - target : 0); + } + }; + var result = SearchUtils.bsearch(num, range, compare); + return result < range.length && compare(range[result], num) === 0; }; /** * Return whether or not the first character is within the named range - * of Unicode characters. The valid list of range names are taken from + * of Unicode characters. The valid list of range names are taken from * the Unicode 6.0 spec. Characters in all ranges of Unicode are supported, - * including those supported in Javascript via UTF-16. Currently, this method + * including those supported in Javascript via UTF-16. Currently, this method * supports the following range names: - * + * *
    *
  • ascii - basic ASCII *
  • latin - Latin, Latin Extended Additional, Latin-1 supplement, Latin Extended-C, Latin Extended-D, Latin Extended-E @@ -134,7 +134,7 @@ CType._inRange = function(num, rangeName, obj) { *
  • osmanya *
  • tifinagh *
  • val - *
  • arabic - Arabic, Arabic Supplement, Arabic Presentation Forms-A, + *
  • arabic - Arabic, Arabic Supplement, Arabic Presentation Forms-A, * Arabic Presentation Forms-B, Arabic Mathematical Alphabetic Symbols *
  • carlan *
  • hebrew @@ -185,13 +185,13 @@ CType._inRange = function(num, rangeName, obj) { *
  • tagbanwa *
  • bopomofo - Bopomofo, Bopomofo Extended *
  • cjk - the CJK unified ideographs (Han), CJK Unified Ideographs - * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs - * Extension C, CJK Unified Ideographs Extension D, Ideographic Description + * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs + * Extension C, CJK Unified Ideographs Extension D, Ideographic Description * Characters (=isIdeo()) - *
  • cjkcompatibility - CJK Compatibility, CJK Compatibility + *
  • cjkcompatibility - CJK Compatibility, CJK Compatibility * Ideographs, CJK Compatibility Forms, CJK Compatibility Ideographs Supplement *
  • cjkradicals - the CJK radicals, KangXi radicals - *
  • hangul - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, + *
  • hangul - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, * Hangul Jamo Extended-B, Hangul Compatibility Jamo *
  • cjkpunct - CJK symbols and punctuation *
  • cjkstrokes - CJK strokes @@ -201,7 +201,7 @@ CType._inRange = function(num, rangeName, obj) { *
  • lisu *
  • yi - Yi Syllables, Yi Radicals *
  • cherokee - *
  • canadian - Unified Canadian Aboriginal Syllabics, Unified Canadian + *
  • canadian - Unified Canadian Aboriginal Syllabics, Unified Canadian * Aboriginal Syllabics Extended *
  • presentation - Alphabetic presentation forms *
  • vertical - Vertical Forms @@ -210,7 +210,7 @@ CType._inRange = function(num, rangeName, obj) { *
  • box - Box Drawing *
  • block - Block Elements *
  • letterlike - Letterlike symbols - *
  • mathematical - Mathematical alphanumeric symbols, Miscellaneous + *
  • mathematical - Mathematical alphanumeric symbols, Miscellaneous * Mathematical Symbols-A, Miscellaneous Mathematical Symbols-B *
  • enclosedalpha - Enclosed alphanumerics, Enclosed Alphanumeric Supplement *
  • enclosedcjk - Enclosed CJK letters and months, Enclosed Ideographic Supplement @@ -219,7 +219,7 @@ CType._inRange = function(num, rangeName, obj) { *
  • controlpictures - Control pictures *
  • misc - Miscellaneous technical *
  • ocr - Optical character recognition (OCR) - *
  • combining - Combining Diacritical Marks, Combining Diacritical Marks + *
  • combining - Combining Diacritical Marks, Combining Diacritical Marks * for Symbols, Combining Diacritical Marks Supplement, Combining Diacritical Marks Extended *
  • digits - ASCII digits (=isDigit()) *
  • indicnumber - Common Indic Number Forms @@ -227,8 +227,8 @@ CType._inRange = function(num, rangeName, obj) { *
  • supersub - Superscripts and Subscripts *
  • arrows - Arrows, Miscellaneous Symbols and Arrows, Supplemental Arrows-A, * Supplemental Arrows-B, Supplemental Arrows-C - *
  • operators - Mathematical operators, supplemental - * mathematical operators + *
  • operators - Mathematical operators, supplemental + * mathematical operators *
  • geometric - Geometric shapes, Geometric shapes extended *
  • ancient - Ancient symbols *
  • braille - Braille patterns @@ -238,7 +238,7 @@ CType._inRange = function(num, rangeName, obj) { *
  • yijing - Yijing Hexagram Symbols *
  • specials *
  • variations - Variation Selectors, Variation Selectors Supplement - *
  • privateuse - Private Use Area, Supplementary Private Use Area-A, + *
  • privateuse - Private Use Area, Supplementary Private Use Area-A, * Supplementary Private Use Area-B *
  • supplementarya - Supplementary private use area-A *
  • supplementaryb - Supplementary private use area-B @@ -263,8 +263,8 @@ CType._inRange = function(num, rangeName, obj) { *
  • pictographs - miscellaneous symbols and pictographs, supplemental symbols and pictographs *
  • ornamentaldingbats - ornamental dingbats *

- * - * + * + * * @protected * @param {string|IString|number} ch character or code point to examine * @param {string} rangeName the name of the range to check @@ -272,25 +272,25 @@ CType._inRange = function(num, rangeName, obj) { * range */ CType.withinRange = function(ch, rangeName) { - if (!rangeName) { - return false; - } - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + if (!rangeName) { + return false; + } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); + return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); }; /** @@ -300,7 +300,7 @@ CType.withinRange = function(ch, rangeName) { * @param {function(*)|undefined} onLoad */ CType._init = function(sync, loadParams, onLoad) { - CType._load("ctype", sync, loadParams, onLoad); + CType._load("ctype", sync, loadParams, onLoad); }; /** @@ -311,27 +311,27 @@ CType._init = function(sync, loadParams, onLoad) { * @param {function(*)|undefined} onLoad */ CType._load = function (name, sync, loadParams, onLoad) { - if (!ilib.data[name]) { - var loadName = name ? name + ".json" : "CType.json"; - Utils.loadData({ - object: "CType", - name: loadName, - locale: "-", - nonlocale: true, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function(ct) { - ilib.data[name] = ct; - if (onLoad && typeof(onLoad) === 'function') { - onLoad(ilib.data[name]); - } - }) - }); - } else { - if (onLoad && typeof(onLoad) === 'function') { - onLoad(ilib.data[name]); - } - } + if (!ilib.data[name]) { + var loadName = name ? name + ".json" : "CType.json"; + Utils.loadData({ + object: "CType", + name: loadName, + locale: "-", + nonlocale: true, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function(ct) { + ilib.data[name] = ct; + if (onLoad && typeof(onLoad) === 'function') { + onLoad(ilib.data[name]); + } + }) + }); + } else { + if (onLoad && typeof(onLoad) === 'function') { + onLoad(ilib.data[name]); + } + } }; module.exports = CType; diff --git a/js/lib/Calendar.js b/js/lib/Calendar.js index cae99b55ab..b7aa0b81da 100644 --- a/js/lib/Calendar.js +++ b/js/lib/Calendar.js @@ -1,6 +1,6 @@ /* * Calendar.js - Represent a calendar object. - * + * * Copyright © 2012-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,11 +19,11 @@ /** * @class - * Superclass for all calendar subclasses that contains shared + * Superclass for all calendar subclasses that contains shared * functionality. This class is never instantiated on its own. Instead, * you should use the {@link CalendarFactory} function to manufacture a new - * instance of a subclass of Calendar. - * + * instance of a subclass of Calendar. + * * @private * @constructor */ @@ -31,57 +31,57 @@ var Calendar = function() { }; /* place for the subclasses to put their constructors so that the factory method - * can find them. Do this to add your calendar after it's defined: + * can find them. Do this to add your calendar after it's defined: * Calendar._constructors["mytype"] = Calendar.MyTypeConstructor; */ Calendar._constructors = {}; Calendar.prototype = { - /** - * Return the type of this calendar. - * - * @return {string} the name of the type of this calendar - */ - getType: function() { - throw "Cannot call methods of abstract class Calendar"; - }, - - /** - * Return the number of months in the given year. The number of months in a year varies - * for some luni-solar calendars because in some years, an extra month is needed to extend the - * days in a year to an entire solar year. The month is represented as a 1-based number - * where 1=first month, 2=second month, etc. - * - * @param {number} year a year for which the number of months is sought - * @return {number} The number of months in the given year - */ - getNumMonths: function(year) { - throw "Cannot call methods of abstract class Calendar"; - }, - - /** - * Return the number of days in a particular month in a particular year. This function - * can return a different number for a month depending on the year because of things - * like leap years. - * - * @param {number} month the month for which the length is sought - * @param {number} year the year within which that month can be found - * @return {number} the number of days within the given month in the given year - */ - getMonLength: function(month, year) { - throw "Cannot call methods of abstract class Calendar"; - }, - - /** - * Return true if the given year is a leap year in this calendar. - * The year parameter may be given as a number. - * - * @param {number} year the year for which the leap year information is being sought - * @return {boolean} true if the given year is a leap year - */ - isLeapYear: function(year) { - throw "Cannot call methods of abstract class Calendar"; - } + /** + * Return the type of this calendar. + * + * @return {string} the name of the type of this calendar + */ + getType: function() { + throw "Cannot call methods of abstract class Calendar"; + }, + + /** + * Return the number of months in the given year. The number of months in a year varies + * for some luni-solar calendars because in some years, an extra month is needed to extend the + * days in a year to an entire solar year. The month is represented as a 1-based number + * where 1=first month, 2=second month, etc. + * + * @param {number} year a year for which the number of months is sought + * @return {number} The number of months in the given year + */ + getNumMonths: function(year) { + throw "Cannot call methods of abstract class Calendar"; + }, + + /** + * Return the number of days in a particular month in a particular year. This function + * can return a different number for a month depending on the year because of things + * like leap years. + * + * @param {number} month the month for which the length is sought + * @param {number} year the year within which that month can be found + * @return {number} the number of days within the given month in the given year + */ + getMonLength: function(month, year) { + throw "Cannot call methods of abstract class Calendar"; + }, + + /** + * Return true if the given year is a leap year in this calendar. + * The year parameter may be given as a number. + * + * @param {number} year the year for which the leap year information is being sought + * @return {boolean} true if the given year is a leap year + */ + isLeapYear: function(year) { + throw "Cannot call methods of abstract class Calendar"; + } }; module.exports = Calendar; \ No newline at end of file diff --git a/js/lib/CalendarFactory.js b/js/lib/CalendarFactory.js index 498ed58f1e..685e20e424 100644 --- a/js/lib/CalendarFactory.js +++ b/js/lib/CalendarFactory.js @@ -1,6 +1,6 @@ /* * CalendarFactory.js - Constructs new instances of the right subclass of Calendar - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,22 +17,22 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); var Calendar = require("./Calendar.js"); /** * Factory method to create a new instance of a calendar subclass.

- * + * * The options parameter can be an object that contains the following * properties: - * + * *

    *
  • type - specify the type of the calendar desired. The - * list of valid values changes depending on which calendars are - * defined. When assembling your iliball.js, include those calendars - * you wish to use in your program or web page, and they will register + * list of valid values changes depending on which calendars are + * defined. When assembling your iliball.js, include those calendars + * you wish to use in your program or web page, and they will register * themselves with this factory method. The "official", "gregorian", * and "julian" calendars are all included by default, as they are the * standard calendars for much of the world. @@ -41,73 +41,73 @@ var Calendar = require("./Calendar.js"); * calendar to a Gregorian-style calendar on a different date for * each country, as the governments of those countries decided to * adopt the Gregorian calendar at different times. - * - *
  • onLoad - a callback function to call when the calendar object is fully + * + *
  • onLoad - a callback function to call when the calendar object is fully * loaded. When the onLoad option is given, the calendar factory will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
  • loadParams - an object containing parameters to pass to the + * + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * + * * If a locale is specified, but no type, then the calendar that is default for * the locale will be instantiated and returned. If neither the type nor * the locale are specified, then the calendar for the default locale will - * be used. - * + * be used. + * * @static * @param {Object=} options options controlling the construction of this instance, or * undefined to use the default options * @return {Calendar} an instance of a calendar object of the appropriate type */ var CalendarFactory = function (options) { - var locale, - type, - sync = true, - instance; - - if (options) { - if (options.locale) { - locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - type = options.type || options.calendar; - - if (typeof(options.sync) === 'boolean') { - sync = options.sync; - } - } - - if (!locale) { - locale = new Locale(); // default locale - } - - if (!type) { - new LocaleInfo(locale, { - sync: sync, - loadParams: options && options.loadParams, - onLoad: function(info) { - type = info.getCalendar(); - - instance = CalendarFactory._init(type, options); - } - }); - } else { - instance = CalendarFactory._init(type, options); - } - - return instance; + var locale, + type, + sync = true, + instance; + + if (options) { + if (options.locale) { + locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + type = options.type || options.calendar; + + if (typeof(options.sync) === 'boolean') { + sync = options.sync; + } + } + + if (!locale) { + locale = new Locale(); // default locale + } + + if (!type) { + new LocaleInfo(locale, { + sync: sync, + loadParams: options && options.loadParams, + onLoad: function(info) { + type = info.getCalendar(); + + instance = CalendarFactory._init(type, options); + } + }); + } else { + instance = CalendarFactory._init(type, options); + } + + return instance; }; /** @@ -116,20 +116,20 @@ var CalendarFactory = function (options) { * @private */ CalendarFactory._dynMap = { - "coptic": "Coptic", - "ethiopic": "Ethiopic", - "gregorian": "Gregorian", - "han": "Han", - "hebrew": "Hebrew", - "islamic": "Islamic", - "julian": "Julian", - "persian": "Persian", - "persian-algo": "PersianAlgo", - "thaisolar": "ThaiSolar" + "coptic": "Coptic", + "ethiopic": "Ethiopic", + "gregorian": "Gregorian", + "han": "Han", + "hebrew": "Hebrew", + "islamic": "Islamic", + "julian": "Julian", + "persian": "Persian", + "persian-algo": "PersianAlgo", + "thaisolar": "ThaiSolar" }; function circumventWebPack(x) { - return "./" + x + "Cal.js"; + return "./" + x + "Cal.js"; } /** @@ -137,55 +137,55 @@ function circumventWebPack(x) { * @protected */ CalendarFactory._dynLoadCalendar = function (name, fnc) { - if (!Calendar._constructors[name]) { - var entry = CalendarFactory._dynMap[name]; - if (entry) { - Calendar._constructors[name] = require(fnc(entry)); - } - } - return Calendar._constructors[name]; + if (!Calendar._constructors[name]) { + var entry = CalendarFactory._dynMap[name]; + if (entry) { + Calendar._constructors[name] = require(fnc(entry)); + } + } + return Calendar._constructors[name]; }; /** @private */ CalendarFactory._init = function(type, options) { - var cons; - - if (ilib.isDynCode()) { - CalendarFactory._dynLoadCalendar(type, circumventWebPack); - } - - cons = Calendar._constructors[type]; - - // pass the same options through to the constructor so the subclass - // has the ability to do something with if it needs to + var cons; + + if (ilib.isDynCode()) { + CalendarFactory._dynLoadCalendar(type, circumventWebPack); + } + + cons = Calendar._constructors[type]; + + // pass the same options through to the constructor so the subclass + // has the ability to do something with if it needs to if (!cons && typeof(options.onLoad) === "function") { options.onLoad(undefined); } - return cons && new cons(options); + return cons && new cons(options); }; /** * Return an array of known calendar types that the factory method can instantiate. - * + * * @return {Array.} an array of calendar types */ CalendarFactory.getCalendars = function () { - var arr = [], - c; - - if (ilib.isDynCode()) { - for (c in CalendarFactory._dynMap) { - CalendarFactory._dynLoadCalendar(c, circumventWebPack); - } - } - - for (c in Calendar._constructors) { - if (c && Calendar._constructors[c]) { - arr.push(c); // code like a pirate - } - } - - return arr; + var arr = [], + c; + + if (ilib.isDynCode()) { + for (c in CalendarFactory._dynMap) { + CalendarFactory._dynLoadCalendar(c, circumventWebPack); + } + } + + for (c in Calendar._constructors) { + if (c && Calendar._constructors[c]) { + arr.push(c); // code like a pirate + } + } + + return arr; }; module.exports = CalendarFactory; diff --git a/js/lib/CaseMapper.js b/js/lib/CaseMapper.js index 641a38228b..cb7603ff32 100644 --- a/js/lib/CaseMapper.js +++ b/js/lib/CaseMapper.js @@ -1,6 +1,6 @@ /* * CaseMapper.js - define upper- and lower-case mapper - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Locale = require("./Locale.js"); var IString = require("./IString.js"); @@ -25,150 +25,150 @@ var IString = require("./IString.js"); /** * @class * Create a new string mapper instance that maps strings to upper or - * lower case. This mapping will work for any string as characters + * lower case. This mapping will work for any string as characters * that have no case will be returned unchanged.

- * + * * The options may contain any of the following properties: - * + * *

    - *
  • locale - locale to use when loading the mapper. Some maps are + *
  • locale - locale to use when loading the mapper. Some maps are * locale-dependent, and this locale selects the right one. Default if this is * not specified is the current locale. - * + * *
  • direction - "toupper" for upper-casing, or "tolower" for lower-casing. * Default if not specified is "toupper". *
- * - * + * + * * @constructor - * @param {Object=} options options to initialize this mapper + * @param {Object=} options options to initialize this mapper */ var CaseMapper = function (options) { - this.up = true; - this.locale = new Locale(); - - if (options) { - if (typeof(options.locale) !== 'undefined') { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - this.up = (!options.direction || options.direction === "toupper"); - } + this.up = true; + this.locale = new Locale(); - this.mapData = this.up ? { - "ß": "SS", // German - 'ΐ': 'Ι', // Greek - 'ά': 'Α', - 'έ': 'Ε', - 'ή': 'Η', - 'ί': 'Ι', - 'ΰ': 'Υ', - 'ϊ': 'Ι', - 'ϋ': 'Υ', - 'ό': 'Ο', - 'ύ': 'Υ', - 'ώ': 'Ω', - 'Ӏ': 'Ӏ', // Russian and slavic languages - 'ӏ': 'Ӏ' - } : { - 'Ӏ': 'Ӏ' // Russian and slavic languages - }; + if (options) { + if (typeof(options.locale) !== 'undefined') { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } - switch (this.locale.getLanguage()) { - case "az": - case "tr": - case "crh": - case "kk": - case "krc": - case "tt": - var lower = "iı"; - var upper = "İI"; - this._setUpMap(lower, upper); - break; - } - - if (ilib._getBrowser() === "ie" || ilib._getBrowser() === "Edge") { - // IE is missing these mappings for some reason - if (this.up) { - this.mapData['ς'] = 'Σ'; - } - this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic - // Georgian Nuskhuri <-> Asomtavruli - this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); - } + this.up = (!options.direction || options.direction === "toupper"); + } + + this.mapData = this.up ? { + "ß": "SS", // German + 'ΐ': 'Ι', // Greek + 'ά': 'Α', + 'έ': 'Ε', + 'ή': 'Η', + 'ί': 'Ι', + 'ΰ': 'Υ', + 'ϊ': 'Ι', + 'ϋ': 'Υ', + 'ό': 'Ο', + 'ύ': 'Υ', + 'ώ': 'Ω', + 'Ӏ': 'Ӏ', // Russian and slavic languages + 'ӏ': 'Ӏ' + } : { + 'Ӏ': 'Ӏ' // Russian and slavic languages + }; + + switch (this.locale.getLanguage()) { + case "az": + case "tr": + case "crh": + case "kk": + case "krc": + case "tt": + var lower = "iı"; + var upper = "İI"; + this._setUpMap(lower, upper); + break; + } + + if (ilib._getBrowser() === "ie" || ilib._getBrowser() === "Edge") { + // IE is missing these mappings for some reason + if (this.up) { + this.mapData['ς'] = 'Σ'; + } + this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic + // Georgian Nuskhuri <-> Asomtavruli + this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); + } }; CaseMapper.prototype = { - /** - * @private - */ - _charMapper: function(string) { - if (!string) { - return string; - } - var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); - var ret = ""; - var it = input.charIterator(); - var c; - - while (it.hasNext()) { - c = it.next(); - if (!this.up && c === 'Σ') { - if (it.hasNext()) { - c = it.next(); - var code = c.charCodeAt(0); - // if the next char is not a greek letter, this is the end of the word so use the - // final form of sigma. Otherwise, use the mid-word form. - ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; - ret += c.toLowerCase(); - } else { - // no next char means this is the end of the word, so use the final form of sigma - ret += 'ς'; - } - } else { - if (this.mapData[c]) { - ret += this.mapData[c]; - } else { - ret += this.up ? c.toUpperCase() : c.toLowerCase(); - } - } - } - - return ret; - }, + /** + * @private + */ + _charMapper: function(string) { + if (!string) { + return string; + } + var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); + var ret = ""; + var it = input.charIterator(); + var c; + + while (it.hasNext()) { + c = it.next(); + if (!this.up && c === 'Σ') { + if (it.hasNext()) { + c = it.next(); + var code = c.charCodeAt(0); + // if the next char is not a greek letter, this is the end of the word so use the + // final form of sigma. Otherwise, use the mid-word form. + ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; + ret += c.toLowerCase(); + } else { + // no next char means this is the end of the word, so use the final form of sigma + ret += 'ς'; + } + } else { + if (this.mapData[c]) { + ret += this.mapData[c]; + } else { + ret += this.up ? c.toUpperCase() : c.toLowerCase(); + } + } + } + + return ret; + }, + + /** @private */ + _setUpMap: function(lower, upper) { + var from, to; + if (this.up) { + from = lower; + to = upper; + } else { + from = upper; + to = lower; + } + for (var i = 0; i < upper.length; i++) { + this.mapData[from[i]] = to[i]; + } + }, - /** @private */ - _setUpMap: function(lower, upper) { - var from, to; - if (this.up) { - from = lower; - to = upper; - } else { - from = upper; - to = lower; - } - for (var i = 0; i < upper.length; i++) { - this.mapData[from[i]] = to[i]; - } - }, + /** + * Return the locale that this mapper was constructed with. + * @returns {Locale} the locale that this mapper was constructed with + */ + getLocale: function () { + return this.locale; + }, - /** - * Return the locale that this mapper was constructed with. - * @returns {Locale} the locale that this mapper was constructed with - */ - getLocale: function () { - return this.locale; - }, - - /** - * Map a string to lower case in a locale-sensitive manner. - * - * @param {string|undefined} string - * @return {string|undefined} - */ - map: function (string) { - return this._charMapper(string); - } + /** + * Map a string to lower case in a locale-sensitive manner. + * + * @param {string|undefined} string + * @return {string|undefined} + */ + map: function (string) { + return this._charMapper(string); + } }; module.exports = CaseMapper; diff --git a/js/lib/Charmap.js b/js/lib/Charmap.js index ff7040ecce..1c2708f933 100644 --- a/js/lib/Charmap.js +++ b/js/lib/Charmap.js @@ -1,6 +1,6 @@ /* * Charmap.js - A character set mapping class - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data charmaps charset/US-ASCII charset/ISO-10646-UCS-2 charset/ISO-8859-1 charset/ISO-8859-15 charmaps/ISO-8859-15 charmaps/ISO-8859-1 charset/ISO-8859-1 -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var IString = require("./IString.js"); @@ -30,15 +30,15 @@ var IString = require("./IString.js"); * mapping. The subclasses implement all other charsets, some algorithmically, and * some in a table-based way. Use {@link CharmapFactory} to create the correct * subclass instance for the desired charmap.

- * + * * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base - * character set and encoding used by Javascript itself. In order to convert - * between two non-Unicode character sets, you must chain two charmap instances together + * character set and encoding used by Javascript itself. In order to convert + * between two non-Unicode character sets, you must chain two charmap instances together * to first map to Unicode and then back to the second charset.

- * - * The options parameter controls which mapping is constructed and its behaviours. The + * + * The options parameter controls which mapping is constructed and its behaviours. The * current list of supported options are: - * + * *

    *
  • missing - specify what to do if a mapping is missing for a particular * character. For example, if you are mapping Unicode characters to a particular native @@ -46,33 +46,33 @@ var IString = require("./IString.js"); * follow the behaviour specified in this property. Valid values are: *
      *
    • skip - skip any characters that do not exist in the target charset - *
    • placeholder - put a static placeholder character in the output string - * wherever there is an unknown character in the input string. Use the placeholder + *
    • placeholder - put a static placeholder character in the output string + * wherever there is an unknown character in the input string. Use the placeholder * parameter to specify which character to use in this case - *
    • escape - use an escape sequence to represent the unknown character + *
    • escape - use an escape sequence to represent the unknown character *
    * The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *
  • placeholder - specify the placeholder character to use when the + * + *
  • placeholder - specify the placeholder character to use when the * mapper cannot map a particular input character to the output string. If this - * option is not specified, then the '?' (question mark) character is used where + * option is not specified, then the '?' (question mark) character is used where * possible. - * + * *
  • escapeStyle - what style of escape sequences should be used to * escape unknown characters in the input when mapping to native, and what - * style of espcae sequences should be parsed when mapping to Unicode. Valid + * style of espcae sequences should be parsed when mapping to Unicode. Valid * values are: *
      *
    • html - Escape the characters as HTML entities. This would use * the standard HTML 5.0 (or later) entity names where possible, and numeric - * entities in all other cases. Eg. an "e" with an acute accent would be + * entities in all other cases. Eg. an "e" with an acute accent would be * "é" *
    • js - Use the Javascript escape style. Eg. an "e" with an acute * accent would be "\u00E9". This can also be specified as "c#" as * it uses a similar escape syntax. *
    • c - Use the C/C++ escape style, which is similar to the the - * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an + * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an * acute accent would be "\x00E9". This can also be specified as "c++". *
    • java - Use the Java escape style. This is very similar to the * the Javascript style, but the backslash has to be escaped twice. Eg. an @@ -83,60 +83,60 @@ var IString = require("./IString.js"); *
    * The default if this style is not specified is "js" for Javascript. *
- * - * If this copy of ilib is pre-assembled and all the data is already available, + * + * If this copy of ilib is pre-assembled and all the data is already available, * or if the data was already previously loaded, then this constructor will call - * the onLoad callback immediately when the initialization is done. + * the onLoad callback immediately when the initialization is done. * If the onLoad option is not given, this class will only attempt to load any * missing data synchronously. - * + * * @constructor * @param {Object=} options options which govern the construction of this instance */ var Charmap = function(options) { - if (options && options.noinstance) { - return; - } - - this.missing = "placeholder"; - this.placeholder = "?"; - this.escapeStyle = "js"; - this.expansionFactor = 1; - - if (options) { - if (typeof(options.placeholder) !== 'undefined') { - this.placeholder = options.placeholder; - } - - var escapes = { - "html": "html", - "js": "js", - "c#": "js", - "c": "c", - "c++": "c", - "java": "java", - "ruby": "java", - "perl": "perl" - }; - - if (typeof(options.escapeStyle) !== 'undefined') { - if (typeof(escapes[options.escapeStyle]) !== 'undefined') { - this.escapeStyle = escapes[options.escapeStyle]; - } - } - - if (typeof(options.missing) !== 'undefined') { - if (options.missing === "skip" || options.missing === "placeholder" || options.missing === "escape") { - this.missing = options.missing; - } - } - } + if (options && options.noinstance) { + return; + } + + this.missing = "placeholder"; + this.placeholder = "?"; + this.escapeStyle = "js"; + this.expansionFactor = 1; + + if (options) { + if (typeof(options.placeholder) !== 'undefined') { + this.placeholder = options.placeholder; + } + + var escapes = { + "html": "html", + "js": "js", + "c#": "js", + "c": "c", + "c++": "c", + "java": "java", + "ruby": "java", + "perl": "perl" + }; + + if (typeof(options.escapeStyle) !== 'undefined') { + if (typeof(escapes[options.escapeStyle]) !== 'undefined') { + this.escapeStyle = escapes[options.escapeStyle]; + } + } + + if (typeof(options.missing) !== 'undefined') { + if (options.missing === "skip" || options.missing === "placeholder" || options.missing === "escape") { + this.missing = options.missing; + } + } + } }; /** - * A place for the algorithmic conversions to register themselves as + * A place for the algorithmic conversions to register themselves as * they are defined. - * + * * @static * @private */ @@ -147,187 +147,187 @@ Charmap.prototype = { * Return the standard name of this charmap. All charmaps map from * Unicode to the native charset, so the name returned from this * function corresponds to the native charset. - * + * * @returns {string} the name of the locale's language in English */ getName: function () { - return this.charset.getName(); + return this.charset.getName(); }, - + /** * @private */ writeNative: function (array, start, value) { - // console.log("Charmap.writeNative: start " + start + " adding " + JSON.stringify(value)); - if (ilib.isArray(value)) { - for (var i = 0; i < value.length; i++) { - array[start+i] = value[i]; - } - - return value.length; - } else { - array[start] = value; - return 1; - } + // console.log("Charmap.writeNative: start " + start + " adding " + JSON.stringify(value)); + if (ilib.isArray(value)) { + for (var i = 0; i < value.length; i++) { + array[start+i] = value[i]; + } + + return value.length; + } else { + array[start] = value; + return 1; + } }, - + /** * @private */ writeNativeString: function (array, start, string) { - // console.log("Charmap.writeNativeString: start " + start + " adding " + JSON.stringify(string)); - for (var i = 0; i < string.length; i++) { - array[start+i] = string.charCodeAt(i); - } - return string.length; + // console.log("Charmap.writeNativeString: start " + start + " adding " + JSON.stringify(string)); + for (var i = 0; i < string.length; i++) { + array[start+i] = string.charCodeAt(i); + } + return string.length; }, - + /** * @private */ _calcExpansionFactor: function() { - var factor = 1; - factor = Math.max(factor, this.charset.getMaxCharWidth()); - switch (this.missing) { - case "placeholder": - if (this.placeholder) { - factor = Math.max(factor, this.placeholder.length); - } - break; - case "escape": - switch (this.escapeStyle) { - case "html": - factor = Math.max(factor, 8); // &#xHHHH; - break; - case "c": - factor = Math.max(factor, 6); // \xHHHH - break; - case "perl": - factor = Math.max(factor, 10); // \N{U+HHHH} - break; - - default: - factor = Math.max(factor, 6); // \uHHHH - break; - } - break; - default: - break; - } - - this.expansionFactor = factor; + var factor = 1; + factor = Math.max(factor, this.charset.getMaxCharWidth()); + switch (this.missing) { + case "placeholder": + if (this.placeholder) { + factor = Math.max(factor, this.placeholder.length); + } + break; + case "escape": + switch (this.escapeStyle) { + case "html": + factor = Math.max(factor, 8); // &#xHHHH; + break; + case "c": + factor = Math.max(factor, 6); // \xHHHH + break; + case "perl": + factor = Math.max(factor, 10); // \N{U+HHHH} + break; + + default: + factor = Math.max(factor, 6); // \uHHHH + break; + } + break; + default: + break; + } + + this.expansionFactor = factor; }, - + /** * @private */ dealWithMissingChar: function(c) { - var seq = ""; - - switch (this.missing) { - case "skip": - // do nothing - break; - - case "escape": - var num = (typeof(c) === 'string') ? c.charCodeAt(0) : c; - var bigc = JSUtils.pad(num.toString(16), 4).toUpperCase(); - switch (this.escapeStyle) { - case "html": - seq = "&#x" + bigc + ";"; - break; - case "c": - seq = "\\x" + bigc; - break; - case "java": - seq = "\\\\u" + bigc; - break; - case "perl": - seq = "\\N{U+" + bigc + "}"; - break; - - default: - case "js": - seq = "\\u" + bigc; - break; - } - break; - - default: - case "placeholder": - seq = this.placeholder; - break; - } - - return seq; + var seq = ""; + + switch (this.missing) { + case "skip": + // do nothing + break; + + case "escape": + var num = (typeof(c) === 'string') ? c.charCodeAt(0) : c; + var bigc = JSUtils.pad(num.toString(16), 4).toUpperCase(); + switch (this.escapeStyle) { + case "html": + seq = "&#x" + bigc + ";"; + break; + case "c": + seq = "\\x" + bigc; + break; + case "java": + seq = "\\\\u" + bigc; + break; + case "perl": + seq = "\\N{U+" + bigc + "}"; + break; + + default: + case "js": + seq = "\\u" + bigc; + break; + } + break; + + default: + case "placeholder": + seq = this.placeholder; + break; + } + + return seq; }, - + /** - * Map a string to the native character set. This string may be - * given as an intrinsic Javascript string object or an IString + * Map a string to the native character set. This string may be + * given as an intrinsic Javascript string object or an IString * object. - * - * @param {string|IString} string string to map to a different - * character set. - * @return {Uint8Array} An array of bytes representing the string + * + * @param {string|IString} string string to map to a different + * character set. + * @return {Uint8Array} An array of bytes representing the string * in the native character set */ mapToNative: function(string) { - if (!string) { - return new Uint8Array(0); - } - - if (this.algorithm) { - return this.algorithm.mapToNative(string); - } - - // the default algorithm is plain old ASCII - var str = (string instanceof IString) ? string : new IString(string); - - // use IString's iterator so that we take care of walking through - // the code points correctly, including the surrogate pairs - var c, i = 0, it = str.iterator(); - var ret = new Uint8Array(str.length * this.expansionFactor); - - while (it.hasNext() && i < ret.length) { - c = it.next(); - if (c < 127) { - ret[i++] = c; - } else { - i += this.writeNativeString(ret, i, this.dealWithMissingChar(c)); - } - } - - return ret; + if (!string) { + return new Uint8Array(0); + } + + if (this.algorithm) { + return this.algorithm.mapToNative(string); + } + + // the default algorithm is plain old ASCII + var str = (string instanceof IString) ? string : new IString(string); + + // use IString's iterator so that we take care of walking through + // the code points correctly, including the surrogate pairs + var c, i = 0, it = str.iterator(); + var ret = new Uint8Array(str.length * this.expansionFactor); + + while (it.hasNext() && i < ret.length) { + c = it.next(); + if (c < 127) { + ret[i++] = c; + } else { + i += this.writeNativeString(ret, i, this.dealWithMissingChar(c)); + } + } + + return ret; }, - + /** - * Map a native string to the standard Javascript charset of UTF-16. - * This string may be given as an array of numbers where each number - * represents a code point in the "from" charset, or as a Uint8Array + * Map a native string to the standard Javascript charset of UTF-16. + * This string may be given as an array of numbers where each number + * represents a code point in the "from" charset, or as a Uint8Array * array of bytes representing the bytes of the string in order. - * - * @param {Array.|Uint8Array} bytes bytes to map to + * + * @param {Array.|Uint8Array} bytes bytes to map to * a Unicode string * @return {string} A string in the standard Javascript charset UTF-16 */ mapToUnicode: function(bytes) { - var ret = ""; - var c, i = 0; - - while (i < bytes.length) { - c = bytes[i]; - - // the default algorithm is plain old ASCII - if (c < 128) { - ret += String.fromCharCode(c); - } else { - // The byte at "i" wasn't ASCII - ret += this.dealWithMissingChar(bytes[i++]); - } - } - - return ret; + var ret = ""; + var c, i = 0; + + while (i < bytes.length) { + c = bytes[i]; + + // the default algorithm is plain old ASCII + if (c < 128) { + ret += String.fromCharCode(c); + } else { + // The byte at "i" wasn't ASCII + ret += this.dealWithMissingChar(bytes[i++]); + } + } + + return ret; } }; diff --git a/js/lib/CharmapFactory.js b/js/lib/CharmapFactory.js index dfebf81b8d..356552d4a6 100644 --- a/js/lib/CharmapFactory.js +++ b/js/lib/CharmapFactory.js @@ -1,7 +1,7 @@ /* - * CharmapFactory.js - Factory class to create the right subclasses of a charmap for any + * CharmapFactory.js - Factory class to create the right subclasses of a charmap for any * given chararacter set. - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var Charset = require("./Charset.js"); @@ -29,59 +29,59 @@ function circumventWebpackCharmap(x) { } /** - * Factory method to create a new instance of a character set mapping (charmap) - * subclass that is appropriate for the requested charset. Charmap instances map strings to + * Factory method to create a new instance of a character set mapping (charmap) + * subclass that is appropriate for the requested charset. Charmap instances map strings to * other character sets. The charsets can be of any type, single-byte, multi-byte, * shifting, etc.

- * + * * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base - * character set and encoding used by Javascript itself. In order to convert - * between two non-Unicode character sets, you must chain two charmap instances together + * character set and encoding used by Javascript itself. In order to convert + * between two non-Unicode character sets, you must chain two charmap instances together * to first map to Unicode and then back to the second charset.

- * - * The options parameter controls which mapping is constructed and its behaviours. The + * + * The options parameter controls which mapping is constructed and its behaviours. The * current list of supported options are: - * + * *

    - *
  • name - the name of the native charset to map to or from. This can be - * given as an {@link Charset} instance or as a string that contains any commonly used name - * for the character set, which is normalized to a standard IANA name. - * If a name is not given, this class will default to the Western European character + *
  • name - the name of the native charset to map to or from. This can be + * given as an {@link Charset} instance or as a string that contains any commonly used name + * for the character set, which is normalized to a standard IANA name. + * If a name is not given, this class will default to the Western European character * set called ISO-8859-15. - * + * *
  • missing - specify what to do if a mapping is missing for a particular * character. For example, if you are mapping Unicode characters to a particular native * character set that does not support particular Unicode characters, the mapper will * follow the behaviour specified in this property. Valid values are: *
      *
    • skip - skip any characters that do not exist in the target charset - *
    • placeholder - put a static placeholder character in the output string - * wherever there is an unknown character in the input string. Use the placeholder + *
    • placeholder - put a static placeholder character in the output string + * wherever there is an unknown character in the input string. Use the placeholder * parameter to specify which character to use in this case - *
    • escape - use an escape sequence to represent the unknown character + *
    • escape - use an escape sequence to represent the unknown character *
    * The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *
  • placeholder - specify the placeholder character to use when the + * + *
  • placeholder - specify the placeholder character to use when the * mapper cannot map a particular input character to the output string. If this - * option is not specified, then the '?' (question mark) character is used where + * option is not specified, then the '?' (question mark) character is used where * possible. - * + * *
  • escapeStyle - what style of escape sequences should be used to * escape unknown characters in the input when mapping to native, and what - * style of espcae sequences should be parsed when mapping to Unicode. Valid + * style of espcae sequences should be parsed when mapping to Unicode. Valid * values are: *
      *
    • html - Escape the characters as HTML entities. This would use * the standard HTML 5.0 (or later) entity names where possible, and numeric - * entities in all other cases. Eg. an "e" with an acute accent would be + * entities in all other cases. Eg. an "e" with an acute accent would be * "é" *
    • js - Use the Javascript escape style. Eg. an "e" with an acute * accent would be "\u00E9". This can also be specified as "c#" as * it uses a similar escape syntax. *
    • c - Use the C/C++ escape style, which is similar to the the - * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an + * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an * acute accent would be "\x00E9". This can also be specified as "c++". *
    • java - Use the Java escape style. This is very similar to the * the Javascript style, but the backslash has to be escaped twice. Eg. an @@ -91,32 +91,32 @@ function circumventWebpackCharmap(x) { * accent would be "\N{U+00E9}" *
    * The default if this style is not specified is "js" for Javascript. - * - *
  • onLoad - a callback function to call when this object is fully + * + *
  • onLoad - a callback function to call when this object is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing data synchronously or + * + *
  • sync - tell whether to load any missing data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, because the instance returned from this constructor will * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * If this copy of ilib is pre-assembled and all the data is already available, + * + * If this copy of ilib is pre-assembled and all the data is already available, * or if the data was already previously loaded, then this constructor will call - * the onLoad callback immediately when the initialization is done. + * the onLoad callback immediately when the initialization is done. * If the onLoad option is not given, this class will only attempt to load any * missing data synchronously. - * + * * @static * @param {Object=} options options controlling the construction of this instance, or * undefined to use the default options @@ -125,51 +125,51 @@ function circumventWebpackCharmap(x) { * requested charset */ var CharmapFactory = function(options) { - var charsetName = (options && options.name) || "ISO-8859-15"; - var sync = true; - - // console.log("CharmapFactory: called with options: " + JSON.stringify(options)); - - if (options) { - if (typeof(options.sync) === 'boolean') { - sync = options.sync; - } - } else { - options = {sync: true}; - } - - var instance; - - new Charset({ - name: charsetName, - sync: sync, - loadParams: options.loadParams, - onLoad: function (charset) { - // name will be normalized already - var cons, name = charset.getName(); - - // console.log("CharmapFactory: normalized charset name: " + name); - - if (!Charmap._algorithms[name] && ilib.isDynCode()) { - // console.log("CharmapFactory: isDynCode. Doing require"); - var entry = CharmapFactory._dynMap[name] || "CharmapTable"; - cons = Charmap._algorithms[name] = require(circumventWebpackCharmap(entry)); - } - - if (!cons) { - cons = Charmap._algorithms[name] || Charmap._algorithms["CharmapTable"]; - } - - // console.log("CharmapFactory: cons is "); console.dir(cons); - - // Pass the same options through to the constructor so the subclass - // has the ability to do something with if it needs to. It should also call - // the onLoad callback when it is done. - instance = cons && new cons(JSUtils.merge(options || {}, {charset: charset})); - } - }); - - return instance; + var charsetName = (options && options.name) || "ISO-8859-15"; + var sync = true; + + // console.log("CharmapFactory: called with options: " + JSON.stringify(options)); + + if (options) { + if (typeof(options.sync) === 'boolean') { + sync = options.sync; + } + } else { + options = {sync: true}; + } + + var instance; + + new Charset({ + name: charsetName, + sync: sync, + loadParams: options.loadParams, + onLoad: function (charset) { + // name will be normalized already + var cons, name = charset.getName(); + + // console.log("CharmapFactory: normalized charset name: " + name); + + if (!Charmap._algorithms[name] && ilib.isDynCode()) { + // console.log("CharmapFactory: isDynCode. Doing require"); + var entry = CharmapFactory._dynMap[name] || "CharmapTable"; + cons = Charmap._algorithms[name] = require(circumventWebpackCharmap(entry)); + } + + if (!cons) { + cons = Charmap._algorithms[name] || Charmap._algorithms["CharmapTable"]; + } + + // console.log("CharmapFactory: cons is "); console.dir(cons); + + // Pass the same options through to the constructor so the subclass + // has the ability to do something with if it needs to. It should also call + // the onLoad callback when it is done. + instance = cons && new cons(JSUtils.merge(options || {}, {charset: charset})); + } + }); + + return instance; }; @@ -180,22 +180,22 @@ var CharmapFactory = function(options) { * @private */ CharmapFactory._dynMap = { - "UTF-8": "UTF8", - "UTF-16": "UTF16LE", - "UTF-16LE": "UTF16LE", - "UTF-16BE": "UTF16BE", - "US-ASCII": "Charmap" - /* - not implemented yet - "ISO-2022-JP": "ISO2022", - "ISO-2022-JP-1": "ISO2022", - "ISO-2022-JP-2": "ISO2022", - "ISO-2022-JP-3": "ISO2022", - "ISO-2022-JP-2004": "ISO2022", - "ISO-2022-CN": "ISO2022", - "ISO-2022-CN-EXT": "ISO2022", - "ISO-2022-KR": "ISO2022" - */ + "UTF-8": "UTF8", + "UTF-16": "UTF16LE", + "UTF-16LE": "UTF16LE", + "UTF-16BE": "UTF16BE", + "US-ASCII": "Charmap" + /* + not implemented yet + "ISO-2022-JP": "ISO2022", + "ISO-2022-JP-1": "ISO2022", + "ISO-2022-JP-2": "ISO2022", + "ISO-2022-JP-3": "ISO2022", + "ISO-2022-JP-2004": "ISO2022", + "ISO-2022-CN": "ISO2022", + "ISO-2022-CN-EXT": "ISO2022", + "ISO-2022-KR": "ISO2022" + */ }; module.exports = CharmapFactory; \ No newline at end of file diff --git a/js/lib/CharmapTable.js b/js/lib/CharmapTable.js index eec982b492..9535997e8d 100644 --- a/js/lib/CharmapTable.js +++ b/js/lib/CharmapTable.js @@ -1,6 +1,6 @@ /* * CharmapTable.js - A character set mapping class that maps using trie table - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data charmaps/ISO-8859-15 charset/ISO-8859-15 -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Charset = require("./Charset.js"); var Charmap = require("./Charmap.js"); @@ -27,59 +27,59 @@ var IString = require("./IString.js"); /** * @class - * Create a new character set mapping instance using based on a trie table. Charmap - * instances map strings to + * Create a new character set mapping instance using based on a trie table. Charmap + * instances map strings to * other character sets. The charsets can be of any type, single-byte, multi-byte, * shifting, etc.

- * + * * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base - * character set and encoding used by Javascript itself. In order to convert - * between two non-Unicode character sets, you must chain two charmap instances together + * character set and encoding used by Javascript itself. In order to convert + * between two non-Unicode character sets, you must chain two charmap instances together * to first map to Unicode and then back to the second charset.

- * - * The options parameter controls which mapping is constructed and its behaviours. The + * + * The options parameter controls which mapping is constructed and its behaviours. The * current list of supported options are: - * + * *

    - *
  • charset - the name of the native charset to map to or from. This can be - * given as an {@link Charset} instance or as a string that contains any commonly used name - * for the character set, which is normalized to a standard IANA name. - * If a name is not given, this class will default to the Western European character + *
  • charset - the name of the native charset to map to or from. This can be + * given as an {@link Charset} instance or as a string that contains any commonly used name + * for the character set, which is normalized to a standard IANA name. + * If a name is not given, this class will default to the Western European character * set called ISO-8859-15. - * + * *
  • missing - specify what to do if a mapping is missing for a particular * character. For example, if you are mapping Unicode characters to a particular native * character set that does not support particular Unicode characters, the mapper will * follow the behaviour specified in this property. Valid values are: *
      *
    • skip - skip any characters that do not exist in the target charset - *
    • placeholder - put a static placeholder character in the output string - * wherever there is an unknown character in the input string. Use the placeholder + *
    • placeholder - put a static placeholder character in the output string + * wherever there is an unknown character in the input string. Use the placeholder * parameter to specify which character to use in this case - *
    • escape - use an escape sequence to represent the unknown character + *
    • escape - use an escape sequence to represent the unknown character *
    * The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *
  • placeholder - specify the placeholder character to use when the + * + *
  • placeholder - specify the placeholder character to use when the * mapper cannot map a particular input character to the output string. If this - * option is not specified, then the '?' (question mark) character is used where + * option is not specified, then the '?' (question mark) character is used where * possible. - * + * *
  • escapeStyle - what style of escape sequences should be used to * escape unknown characters in the input when mapping to native, and what - * style of espcae sequences should be parsed when mapping to Unicode. Valid + * style of espcae sequences should be parsed when mapping to Unicode. Valid * values are: *
      *
    • html - Escape the characters as HTML entities. This would use * the standard HTML 5.0 (or later) entity names where possible, and numeric - * entities in all other cases. Eg. an "e" with an acute accent would be + * entities in all other cases. Eg. an "e" with an acute accent would be * "é" *
    • js - Use the Javascript escape style. Eg. an "e" with an acute * accent would be "\u00E9". This can also be specified as "c#" as * it uses a similar escape syntax. *
    • c - Use the C/C++ escape style, which is similar to the the - * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an + * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an * acute accent would be "\x00E9". This can also be specified as "c++". *
    • java - Use the Java escape style. This is very similar to the * the Javascript style, but the backslash has to be escaped twice. Eg. an @@ -89,32 +89,32 @@ var IString = require("./IString.js"); * accent would be "\N{U+00E9}" *
    * The default if this style is not specified is "js" for Javascript. - * - *
  • onLoad - a callback function to call when this object is fully + * + *
  • onLoad - a callback function to call when this object is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing data synchronously or + * + *
  • sync - tell whether to load any missing data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, because the instance returned from this constructor will * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * If this copy of ilib is pre-assembled and all the data is already available, + * + * If this copy of ilib is pre-assembled and all the data is already available, * or if the data was already previously loaded, then this constructor will call - * the onLoad callback immediately when the initialization is done. + * the onLoad callback immediately when the initialization is done. * If the onLoad option is not given, this class will only attempt to load any * missing data synchronously. - * + * * @constructor * @see {ilib.setLoaderCallback} for information about registering a loader callback instance * @extends Charmap @@ -164,14 +164,14 @@ CharmapTable.prototype.constructor = CharmapTable; */ CharmapTable.prototype._init = function(options) { this._calcExpansionFactor(); - + Utils.loadData({ - object: "Charmap", + object: "Charmap", locale: "-", nonlocale: true, - name: "charmaps/" + this.charset.getName() + ".json", - sync: options.sync, - loadParams: options.loadParams, + name: "charmaps/" + this.charset.getName() + ".json", + sync: options.sync, + loadParams: options.loadParams, callback: ilib.bind(this, function (mapping) { var ret = this; if (!mapping) { @@ -196,134 +196,134 @@ CharmapTable.prototype._init = function(options) { * @private */ CharmapTable.prototype._trieWalk = function(trie, array, start) { - function isValue(node) { - return (typeof(node) === 'string' || typeof(node) === 'number' || - (typeof(node) === 'object' && ilib.isArray(node))); - } - - var lastLeaf = undefined, - i = start, - trienode = trie; - - while (i < array.length) { - if (typeof(trienode.__leaf) !== 'undefined') { - lastLeaf = { - consumed: i - start + 1, - value: trienode.__leaf - }; - } - if (array[i] === 0) { - // null-terminator, so end the mapping. - return { - consumed: 1, - value: 0 - }; - } else if (typeof(trienode[array[i]]) !== 'undefined') { - // we have a mapping - if (isValue(trienode[array[i]])) { - // it is a leaf node - return { - consumed: i - start + 1, - value: trienode[array[i]] - }; - } else { - // it is an intermediate node - trienode = trienode[array[i++]]; - } - } else { - // no mapping for this array element, so return the last known - // leaf. If none, this will return undefined. - return lastLeaf; - } - } + function isValue(node) { + return (typeof(node) === 'string' || typeof(node) === 'number' || + (typeof(node) === 'object' && ilib.isArray(node))); + } + + var lastLeaf = undefined, + i = start, + trienode = trie; + + while (i < array.length) { + if (typeof(trienode.__leaf) !== 'undefined') { + lastLeaf = { + consumed: i - start + 1, + value: trienode.__leaf + }; + } + if (array[i] === 0) { + // null-terminator, so end the mapping. + return { + consumed: 1, + value: 0 + }; + } else if (typeof(trienode[array[i]]) !== 'undefined') { + // we have a mapping + if (isValue(trienode[array[i]])) { + // it is a leaf node + return { + consumed: i - start + 1, + value: trienode[array[i]] + }; + } else { + // it is an intermediate node + trienode = trienode[array[i++]]; + } + } else { + // no mapping for this array element, so return the last known + // leaf. If none, this will return undefined. + return lastLeaf; + } + } - return undefined; + return undefined; }; - + /** - * Map a string to the native character set. This string may be - * given as an intrinsic Javascript string object or an IString + * Map a string to the native character set. This string may be + * given as an intrinsic Javascript string object or an IString * object. - * - * @param {string|IString} string string to map to a different - * character set. - * @return {Uint8Array} An array of bytes representing the string + * + * @param {string|IString} string string to map to a different + * character set. + * @return {Uint8Array} An array of bytes representing the string * in the native character set */ CharmapTable.prototype.mapToNative = function(string) { - if (!string) { - return new Uint8Array(0); - } - - var str = (string instanceof IString) ? string : new IString(string); - - // use IString's iterator so that we take care of walking through - // the code points correctly, including the surrogate pairs - // var c, i = 0, it = str.charIterator(); - var ret = new Uint8Array(str.length * this.expansionFactor); - - var i = 0, j = 0; - - while (i < string.length) { - var result = this._trieWalk(this.map.from, string, i); - if (result) { - if (result.value) { - i += result.consumed; - j += this.writeNative(ret, j, result.value); - } else { - // null-termination - i = string.length; - this.writeNative(ret, j, [result.value]); - } - } else { - // The unicode char at "i" didn't have any mapping, so - // deal with the missing char - j += this.writeNativeString(ret, j, this.dealWithMissingChar(string[i++])); - } - } + if (!string) { + return new Uint8Array(0); + } - return ret.subarray(0, j); + var str = (string instanceof IString) ? string : new IString(string); + + // use IString's iterator so that we take care of walking through + // the code points correctly, including the surrogate pairs + // var c, i = 0, it = str.charIterator(); + var ret = new Uint8Array(str.length * this.expansionFactor); + + var i = 0, j = 0; + + while (i < string.length) { + var result = this._trieWalk(this.map.from, string, i); + if (result) { + if (result.value) { + i += result.consumed; + j += this.writeNative(ret, j, result.value); + } else { + // null-termination + i = string.length; + this.writeNative(ret, j, [result.value]); + } + } else { + // The unicode char at "i" didn't have any mapping, so + // deal with the missing char + j += this.writeNativeString(ret, j, this.dealWithMissingChar(string[i++])); + } + } + + return ret.subarray(0, j); }; /** - * Map a native string to the standard Javascript charset of UTF-16. - * This string may be given as an array of numbers where each number - * represents a code point in the "from" charset, or as a Uint8Array + * Map a native string to the standard Javascript charset of UTF-16. + * This string may be given as an array of numbers where each number + * represents a code point in the "from" charset, or as a Uint8Array * array of bytes representing the bytes of the string in order. - * - * @param {Array.|Uint8Array} bytes bytes to map to + * + * @param {Array.|Uint8Array} bytes bytes to map to * a Unicode string * @return {string} A string in the standard Javascript charset UTF-16 */ CharmapTable.prototype.mapToUnicode = function(bytes) { - var ret = ""; - var i = 0; - - while (i < bytes.length) { - var result = this._trieWalk(this.map.to, bytes, i); - if (result) { - if (result.value) { - i += result.consumed; - if (typeof(result.value) === 'string') { - ret += result.value; - } else if (ilib.isArray(result.value)) { - for (var j = 0; j < result.value.length; j++) { - ret += result.value[j]; - } - } // else error in charmap file?? - } else { - // null-termination - i = bytes.length; - } - } else { - // The byte at "i" wasn't a lead byte, so start again at the - // next byte instead. This may synchronize the rest - // of the string. - ret += this.dealWithMissingChar(bytes[i++]); - } - } + var ret = ""; + var i = 0; + + while (i < bytes.length) { + var result = this._trieWalk(this.map.to, bytes, i); + if (result) { + if (result.value) { + i += result.consumed; + if (typeof(result.value) === 'string') { + ret += result.value; + } else if (ilib.isArray(result.value)) { + for (var j = 0; j < result.value.length; j++) { + ret += result.value[j]; + } + } // else error in charmap file?? + } else { + // null-termination + i = bytes.length; + } + } else { + // The byte at "i" wasn't a lead byte, so start again at the + // next byte instead. This may synchronize the rest + // of the string. + ret += this.dealWithMissingChar(bytes[i++]); + } + } - return ret; + return ret; }; Charmap._algorithms["CharmapTable"] = CharmapTable; diff --git a/js/lib/Charset.js b/js/lib/Charset.js index 56380c3869..25d2c3723e 100644 --- a/js/lib/Charset.js +++ b/js/lib/Charset.js @@ -1,6 +1,6 @@ /* * Charset.js - Return information about a particular character set - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data charset charsetaliases charset/ISO-8859-1 charset/ISO-8859-15 charset/UTF-8 -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); /** @@ -27,144 +27,144 @@ var Utils = require("./Utils.js"); * Create a new character set info instance. Charset instances give information about * a particular character set, such as whether or not it is single byte or multibyte, * and which languages commonly use that charset.

- * + * * The optional options object holds extra parameters if they are necessary. The * current list of supported options are: - * + * *

    *
  • name - the name of the charset. This can be given as any commonly - * used name for the character set, which is normalized to a standard IANA name + * used name for the character set, which is normalized to a standard IANA name * before its info is loaded. If a name is not given, * this class will return information about the base character set of Javascript, * which is currently Unicode as encoded in UTF-16. - * - *
  • onLoad - a callback function to call when this object is fully + * + *
  • onLoad - a callback function to call when this object is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing data synchronously or + * + *
  • sync - tell whether to load any missing data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, because the instance returned from this constructor will * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * If this copy of ilib is pre-assembled and all the data is already available, + * + * If this copy of ilib is pre-assembled and all the data is already available, * or if the data was already previously loaded, then this constructor will call - * the onLoad callback immediately when the initialization is done. + * the onLoad callback immediately when the initialization is done. * If the onLoad option is not given, this class will only attempt to load any * missing data synchronously. - * + * * @constructor * @see {ilib.setLoaderCallback} for information about registering a loader callback instance * @param {Object=} options options which govern the construction of this instance */ var Charset = function(options) { - var sync = true, - loadParams = undefined; - this.originalName = "UTF-8"; - - if (options) { - if (typeof(options.name) !== 'undefined') { - this.originalName = options.name; - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - loadParams = options.loadParams; - } - } - - // default data. A majority of charsets use this info - this.info = { - description: "default", - min: 1, - max: 1, - bigendian: true, - scripts: ["Latn"], - locales: ["*"] - }; - - Utils.loadData({ - object: "Charset", - locale: "-", - nonlocale: true, - name: "charsetaliases.json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - // first map the given original name to one of the standardized IANA names - if (info) { - // recognize better by getting rid of extraneous crap and upper-casing - // it so that the match is case-insensitive - var n = this.originalName.replace(/[-_,:\+\.\(\)]/g, '').toUpperCase(); - this.name = info[n]; - } - if (!this.name) { - this.name = this.originalName; - } - Utils.loadData({ - object: "Charset", - locale: "-", - nonlocale: true, - name: "charset/" + this.name + ".json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - if (info) { - ilib.extend(this.info, info); - } - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - }) - }); + var sync = true, + loadParams = undefined; + this.originalName = "UTF-8"; + + if (options) { + if (typeof(options.name) !== 'undefined') { + this.originalName = options.name; + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + loadParams = options.loadParams; + } + } + + // default data. A majority of charsets use this info + this.info = { + description: "default", + min: 1, + max: 1, + bigendian: true, + scripts: ["Latn"], + locales: ["*"] + }; + + Utils.loadData({ + object: "Charset", + locale: "-", + nonlocale: true, + name: "charsetaliases.json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + // first map the given original name to one of the standardized IANA names + if (info) { + // recognize better by getting rid of extraneous crap and upper-casing + // it so that the match is case-insensitive + var n = this.originalName.replace(/[-_,:\+\.\(\)]/g, '').toUpperCase(); + this.name = info[n]; + } + if (!this.name) { + this.name = this.originalName; + } + Utils.loadData({ + object: "Charset", + locale: "-", + nonlocale: true, + name: "charset/" + this.name + ".json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + if (info) { + ilib.extend(this.info, info); + } + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + }) + }); }; Charset.prototype = { /** - * Return the standard normalized name of this charset. The list of standard names - * comes from the IANA registry of character set names at + * Return the standard normalized name of this charset. The list of standard names + * comes from the IANA registry of character set names at * http://www.iana.org/assignments/character-sets/character-sets.xhtml. - * + * * @returns {string} the name of the charset */ getName: function () { - return this.name; + return this.name; }, - + /** * Return the original name that this instance was constructed with before it was * normalized to the standard name returned by {@link #getName}. - * + * * @returns {string} the original name that this instance was constructed with */ getOriginalName: function() { - return this.originalName; + return this.originalName; }, - + /** * Return a short description of the character set. - * + * * @returns {string} a description of the character set */ getDescription: function() { - return this.info.description || this.getName(); + return this.info.description || this.getName(); }, - + /** * Return the smallest number of bytes that a single character in this charset * could use. For most charsets, this is 1, but for some charsets such as Unicode @@ -173,9 +173,9 @@ Charset.prototype = { * this charset uses */ getMinCharWidth: function () { - return this.info.min; + return this.info.min; }, - + /** * Return the largest number of bytes that a single character in this charset * could use. @@ -183,40 +183,40 @@ Charset.prototype = { * this charset uses */ getMaxCharWidth: function () { - return this.info.max; + return this.info.max; }, - + /** * Return true if this is a multibyte character set, or false for a fixed * width character set. A multibyte character set is one in which the characters * have a variable width. That is, one character may use 1 byte and a different * character might use 2 or 3 bytes. - * + * * @returns {boolean} true if this is a multibyte charset, or false otherwise */ isMultibyte: function() { - return this.getMaxCharWidth() > this.getMinCharWidth(); + return this.getMaxCharWidth() > this.getMinCharWidth(); }, - + /** * Return whether or not characters larger than 1 byte use the big endian order * or little endian. - * + * * @returns {boolean} true if this character set uses big endian order, or false * otherwise */ isBigEndian: function() { - return this.info.bigendian; + return this.info.bigendian; }, - + /** - * Return an array of ISO script codes whose characters can be encoded with this + * Return an array of ISO script codes whose characters can be encoded with this * character set. - * + * * @returns {Array.} an array of ISO script codes supported by this charset */ getScripts: function() { - return this.info.scripts; + return this.info.scripts; } }; diff --git a/js/lib/CodePointSource.js b/js/lib/CodePointSource.js index b9525a39e6..d980dd6519 100644 --- a/js/lib/CodePointSource.js +++ b/js/lib/CodePointSource.js @@ -1,6 +1,6 @@ /* * CodePointSource.js - Source of code points from a string - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,9 +24,9 @@ var NormString = require("./NormString.js"); * @class * Represents a buffered source of code points. The input string is first * normalized so that combining characters come out in a standardized order. - * If the "ignorePunctuation" flag is turned on, then punctuation + * If the "ignorePunctuation" flag is turned on, then punctuation * characters are skipped. - * + * * @constructor * @private * @param {NormString|string} str a string to get code points from @@ -34,53 +34,53 @@ var NormString = require("./NormString.js"); * characters */ var CodePointSource = function(str, ignorePunctuation) { - this.chars = []; - // first convert the string to a normalized sequence of characters - var s = (typeof(str) === "string") ? new NormString(str) : str; - this.it = s.charIterator(); - this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; + this.chars = []; + // first convert the string to a normalized sequence of characters + var s = (typeof(str) === "string") ? new NormString(str) : str; + this.it = s.charIterator(); + this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; }; /** * Return the first num code points in the source without advancing the * source pointer. If there are not enough code points left in the - * string to satisfy the request, this method will return undefined. - * + * string to satisfy the request, this method will return undefined. + * * @param {number} num the number of characters to peek ahead * @return {string|undefined} a string formed out of up to num code points from * the start of the string, or undefined if there are not enough character left * in the source to complete the request */ CodePointSource.prototype.peek = function(num) { - if (num < 1) { - return undefined; - } - if (this.chars.length < num && this.it.hasNext()) { - for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { - var c = this.it.next(); - if (c && !this.ignorePunctuation || !isPunct(c)) { - this.chars.push(c); - } - } - } - if (this.chars.length < num) { - return undefined; - } - return this.chars.slice(0, num).join(""); + if (num < 1) { + return undefined; + } + if (this.chars.length < num && this.it.hasNext()) { + for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { + var c = this.it.next(); + if (c && !this.ignorePunctuation || !isPunct(c)) { + this.chars.push(c); + } + } + } + if (this.chars.length < num) { + return undefined; + } + return this.chars.slice(0, num).join(""); }; /** * Advance the source pointer by the given number of code points. * @param {number} num number of code points to advance */ CodePointSource.prototype.consume = function(num) { - if (num > 0) { - this.peek(num); // for the iterator to go forward if needed - if (num < this.chars.length) { - this.chars = this.chars.slice(num); - } else { - this.chars = []; - } - } + if (num > 0) { + this.peek(num); // for the iterator to go forward if needed + if (num < this.chars.length) { + this.chars = this.chars.slice(num); + } else { + this.chars = []; + } + } }; module.exports = CodePointSource; diff --git a/js/lib/Collator.js b/js/lib/Collator.js index 4db19f7c1b..ee932e7789 100644 --- a/js/lib/Collator.js +++ b/js/lib/Collator.js @@ -1,6 +1,6 @@ /* * Collator.js - Collation routines - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data collation -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); @@ -32,132 +32,132 @@ var GlyphString = require("./GlyphString.js"); /** * @class - * A class that implements a locale-sensitive comparator function + * A class that implements a locale-sensitive comparator function * for use with sorting function. The comparator function * assumes that the strings it is comparing contain Unicode characters * encoded in UTF-16.

- * - * Collations usually depend only on the language, because most collation orders + * + * Collations usually depend only on the language, because most collation orders * are shared between locales that speak the same language. There are, however, a * number of instances where a locale collates differently than other locales * that share the same language. There are also a number of instances where a * locale collates differently based on the script used. This object can handle * these cases automatically if a full locale is specified in the options rather * than just a language code.

- * + * *

Options

- * + * * The options parameter can contain any of the following properties: - * + * *
    - *
  • locale - String|Locale. The locale which the comparator function + *
  • locale - String|Locale. The locale which the comparator function * will collate with. Default: the current iLib locale. - * - *
  • sensitivity - String. Sensitivity or strength of collator. This is one of - * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or + * + *
  • sensitivity - String. Sensitivity or strength of collator. This is one of + * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or * "variant". Default: "primary" *
      *
    1. base or primary - Only the primary distinctions between characters are significant. - * Another way of saying that is that the collator will be case-, accent-, and + * Another way of saying that is that the collator will be case-, accent-, and * variation-insensitive, and only distinguish between the base characters *
    2. case or secondary - Both the primary and secondary distinctions between characters * are significant. That is, the collator will be accent- and variation-insensitive * and will distinguish between base characters and character case. *
    3. accent or tertiary - The primary, secondary, and tertiary distinctions between - * characters are all significant. That is, the collator will be - * variation-insensitive, but accent-, case-, and base-character-sensitive. + * characters are all significant. That is, the collator will be + * variation-insensitive, but accent-, case-, and base-character-sensitive. *
    4. variant or quaternary - All distinctions between characters are significant. That is, * the algorithm is base character-, case-, accent-, and variation-sensitive. *
    - * + * *
  • upperFirst - boolean. When collating case-sensitively in a script that * has the concept of case, put upper-case * characters first, otherwise lower-case will come first. Warning: some browsers do - * not implement this feature or at least do not implement it properly, so if you are + * not implement this feature or at least do not implement it properly, so if you are * using the native collator with this option, you may get different results in different - * browsers. To guarantee the same results, set useNative to false to use the ilib - * collator implementation. This of course will be somewhat slower, but more + * browsers. To guarantee the same results, set useNative to false to use the ilib + * collator implementation. This of course will be somewhat slower, but more * predictable. Default: true - * + * *
  • reverse - boolean. Return the list sorted in reverse order. When the - * upperFirst option is also set to true, upper-case characters would then come at + * upperFirst option is also set to true, upper-case characters would then come at * the end of the list. Default: false. - * + * *
  • scriptOrder - string. When collating strings in multiple scripts, * this property specifies what order those scripts should be sorted. The default * Unicode Collation Algorithm (UCA) already has a default order for scripts, but - * this can be tailored via this property. The value of this option is a + * this can be tailored via this property. The value of this option is a * space-separated list of ISO 15924 scripts codes. If a code is specified in this * property, its default data must be included using the JS assembly tool. If the * data is not included, the ordering for the script will be ignored. Default: - * the default order defined by the UCA. - * + * the default order defined by the UCA. + * *
  • style - The value of the style parameter is dependent on the locale. * For some locales, there are different styles of collating strings depending - * on what kind of strings are being collated or what the preference of the user + * on what kind of strings are being collated or what the preference of the user * is. For example, in German, there is a phonebook order and a dictionary ordering * that sort the same array of strings slightly differently. * The static method {@link Collator#getAvailableStyles} will return a list of styles that ilib - * currently knows about for any given locale. If the value of the style option is + * currently knows about for any given locale. If the value of the style option is * not recognized for a locale, it will be ignored. Default style is "standard".

    - * + * *

  • usage - Whether this collator will be used for searching or sorting. * Valid values are simply the strings "sort" or "search". When used for sorting, - * it is good idea if a collator produces a stable sort. That is, the order of the + * it is good idea if a collator produces a stable sort. That is, the order of the * sorted array of strings should not depend on the order of the strings in the - * input array. As such, when a collator is supposed to act case insensitively, + * input array. As such, when a collator is supposed to act case insensitively, * it nonetheless still distinguishes between case after all other criteria * are satisfied so that strings that are distinguished only by case do not sort - * randomly. For searching, we would like to match two strings that different only - * by case, so the collator must return equals in that situation instead of + * randomly. For searching, we would like to match two strings that different only + * by case, so the collator must return equals in that situation instead of * further distinguishing by case. Default is "sort". - * + * *
  • numeric - Treat the left and right strings as if they started with * numbers and sort them numerically rather than lexically. - * + * *
  • ignorePunctuation - Skip punctuation characters when comparing the * strings. - * - *
  • onLoad - a callback function to call when the collator object is fully + * + *
  • onLoad - a callback function to call when the collator object is fully * loaded. When the onLoad option is given, the collator object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. - * + * *
  • useNative - when this option is true, use the native Intl object * provided by the Javascript engine, if it exists, to implement this class. If - * it doesn't exist, or if this parameter is false, then this class uses a pure - * Javascript implementation, which is slower and uses a lot more memory, but + * it doesn't exist, or if this parameter is false, then this class uses a pure + * Javascript implementation, which is slower and uses a lot more memory, but * works everywhere that ilib works. Default is "true". *
- * + * *

Operation

- * - * The Collator constructor returns a collator object tailored with the above - * options. The object contains an internal compare() method which compares two + * + * The Collator constructor returns a collator object tailored with the above + * options. The object contains an internal compare() method which compares two * strings according to those options. This can be used directly to compare * two strings, but is not useful for passing to the javascript sort function - * because then it will not have its collation data available. Instead, use the + * because then it will not have its collation data available. Instead, use the * getComparator() method to retrieve a function that is bound to the collator - * object. (You could also bind it yourself using ilib.bind()). The bound function - * can be used with the standard Javascript array sorting algorithm, or as a + * object. (You could also bind it yourself using ilib.bind()). The bound function + * can be used with the standard Javascript array sorting algorithm, or as a * comparator with your own sorting algorithm.

- * + * * Example using the standard Javascript array sorting call with the bound * function:

- * + * * *

  * var arr = ["ö", "oe", "ü", "o", "a", "ae", "u", "ß", "ä"];
@@ -167,20 +167,20 @@ var GlyphString = require("./GlyphString.js");
  * 
* *

- * + * * Would give the output:

- * + * * *

  * ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"]
  * 
* - * - * When sorting an array of Javascript objects according to one of the - * string properties of the objects, wrap the collator's compare function + * + * When sorting an array of Javascript objects according to one of the + * string properties of the objects, wrap the collator's compare function * in your own comparator function that knows the structure of the objects * being sorted:

- * + * * *

  * var collator = new Collator({locale: 'de-DE'});
@@ -188,237 +188,237 @@ var GlyphString = require("./GlyphString.js");
  *   var comparator = collator.getComparator();
  *   // left and right are your own objects
  *   return function (left, right) {
- *   	return comparator(left.x.y.textProperty, right.x.y.textProperty);
+ *       return comparator(left.x.y.textProperty, right.x.y.textProperty);
  *   };
  * };
  * arr.sort(myComparator(collator));
  * 
* *

- * + * *

Sort Keys

- * + * * The collator class also has a method to retrieve the sort key for a - * string. The sort key is an array of values that represent how each + * string. The sort key is an array of values that represent how each * character in the string should be collated according to the characteristics - * of the collation algorithm and the given options. Thus, sort keys can be - * compared directly value-for-value with other sort keys that were generated - * by the same collator, and the resulting ordering is guaranteed to be the + * of the collation algorithm and the given options. Thus, sort keys can be + * compared directly value-for-value with other sort keys that were generated + * by the same collator, and the resulting ordering is guaranteed to be the * same as if the original strings were compared by the collator. * Sort keys generated by different collators are not guaranteed to give - * any reasonable results when compared together unless the two collators - * were constructed with - * exactly the same options and therefore end up representing the exact same + * any reasonable results when compared together unless the two collators + * were constructed with + * exactly the same options and therefore end up representing the exact same * collation sequence.

- * + * * A good rule of thumb is that you would use a sort key if you had 10 or more - * items to sort or if your array might be resorted arbitrarily. For example, if your + * items to sort or if your array might be resorted arbitrarily. For example, if your * user interface was displaying a table with 100 rows in it, and each row had * 4 sortable text columns which could be sorted in acending or descending order, * the recommended practice would be to generate a sort key for each of the 4 * sortable fields in each row and store that in the Javascript representation of the * table data. Then, when the user clicks on a column header to resort the - * table according to that column, the resorting would be relatively quick - * because it would only be comparing arrays of values, and not recalculating + * table according to that column, the resorting would be relatively quick + * because it would only be comparing arrays of values, and not recalculating * the collation values for each character in each string for every comparison.

- * + * * For tables that are large, it is usually a better idea to do the sorting * on the server side, especially if the table is the result of a database * query. In this case, the table is usually a view of the cursor of a large * results set, and only a few entries are sent to the front end at a time. * In order to sort the set efficiently, it should be done on the database * level instead. - * + * *

Data

- * + * * Doing correct collation entails a huge amount of mapping data, much of which is * not necessary when collating in one language with one script, which is the most * common case. Thus, ilib implements a number of ways to include the data you * need or leave out the data you don't need using the JS assembly tool: - * + * *
    - *
  1. Full multilingual data - if you are sorting multilingual data and need to collate - * text written in multiple scripts, you can use the directive "!data collation/ducet" to - * load in the full collation data. This allows the collator to perform the entire - * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element - * Table (DUCET). The data is very large, on the order of multiple megabytes, but + *
  2. Full multilingual data - if you are sorting multilingual data and need to collate + * text written in multiple scripts, you can use the directive "!data collation/ducet" to + * load in the full collation data. This allows the collator to perform the entire + * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element + * Table (DUCET). The data is very large, on the order of multiple megabytes, but * sometimes it is necessary. - *
  3. A few scripts - if you are sorting text written in only a few scripts, you may + *
  4. A few scripts - if you are sorting text written in only a few scripts, you may * want to include only the data for those scripts. Each ISO 15924 script code has its * own data available in a separate file, so you can use the data directive to include - * only the data for the scripts you need. For example, use + * only the data for the scripts you need. For example, use * "!data collation/Latn" to retrieve the collation information for the Latin script. - * Because the "ducet" table mentioned in the previous point is a superset of the - * tables for all other scripts, you do not need to include explicitly the data for - * any particular script when using "ducet". That is, you either include "ducet" or + * Because the "ducet" table mentioned in the previous point is a superset of the + * tables for all other scripts, you do not need to include explicitly the data for + * any particular script when using "ducet". That is, you either include "ducet" or * you include a specific list of scripts. *
  5. Only one script - if you are sorting text written only in one script, you can - * either include the data directly as in the previous point, or you can rely on the + * either include the data directly as in the previous point, or you can rely on the * locale to include the correct data for you. In this case, you can use the directive * "!data collate" to load in the locale's collation data for its most common script. *
- * + * * With any of the above ways of including the data, the collator will only perform the * correct language-sensitive sorting for the given locale. All other scripts will be * sorted in the default manner according to the UCA. For example, if you include the * "ducet" data and pass in "de-DE" (German for Germany) as the locale spec, then * only the Latin script (the default script for German) will be sorted according to - * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use + * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use * the default UCA collation rules.

- * + * * If this collator encounters a character for which it has no collation data, it will * sort those characters by pure Unicode value after all characters for which it does have * collation data. For example, if you only loaded in the German collation data (ie. the * data for the Latin script tailored to German) to sort a list of person names, but that - * list happens to include the names of a few Japanese people written in Japanese + * list happens to include the names of a few Japanese people written in Japanese * characters, the Japanese names will sort at the end of the list after all German names, * and will sort according to the Unicode values of the characters. - * + * * @constructor - * @param {Object} options options governing how the resulting comparator + * @param {Object} options options governing how the resulting comparator * function will operate */ var Collator = function(options) { - var sync = true, - loadParams = undefined, - useNative = true; - - // defaults - /** - * @private - * @type {Locale} - */ - this.locale = new Locale(ilib.getLocale()); - - /** @private */ - this.caseFirst = "upper"; - /** @private */ - this.sensitivity = "variant"; - /** @private */ - this.level = 4; - /** @private */ - this.usage = "sort"; - /** @private */ - this.reverse = false; - /** @private */ - this.numeric = false; - /** @private */ - this.style = "default"; - /** @private */ - this.ignorePunctuation = false; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - if (options.sensitivity) { - switch (options.sensitivity) { - case 'primary': - case 'base': - this.sensitivity = "base"; - this.level = 1; - break; - case 'secondary': - case 'accent': - this.sensitivity = "accent"; - this.level = 2; - break; - case 'tertiary': - case 'case': - this.sensitivity = "case"; - this.level = 3; - break; - case 'quaternary': - case 'variant': - this.sensitivity = "variant"; - this.level = 4; - break; - } - } - if (typeof(options.upperFirst) !== 'undefined') { - this.caseFirst = options.upperFirst ? "upper" : "lower"; - } - - if (typeof(options.ignorePunctuation) !== 'undefined') { - this.ignorePunctuation = options.ignorePunctuation; - } - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - loadParams = options.loadParams; - if (typeof(options.useNative) !== 'undefined') { - useNative = options.useNative; - } - - if (options.usage === "sort" || options.usage === "search") { - this.usage = options.usage; - } - - if (typeof(options.reverse) === 'boolean') { - this.reverse = options.reverse; - } - - if (typeof(options.numeric) === 'boolean') { - this.numeric = options.numeric; - } - - if (typeof(options.style) === 'string') { - this.style = options.style; - } - } else { - options = {sync: true}; - } - - if (this.usage === "sort") { - // produces a stable sort - this.level = 4; - } - - if (useNative && typeof(Intl) !== 'undefined' && Intl) { - // this engine is modern and supports the new Intl object! - //console.log("implemented natively"); - /** - * @private - * @type {{compare:function(string,string)}} - */ - this.collator = new Intl.Collator(this.locale.getSpec(), { - sensitivity: this.sensitivity, - caseFirst: this.caseFirst, - ignorePunctuation: this.ignorePunctuation, - numeric: this.numeric, - usage: this.usage - }); - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } else { - //console.log("implemented in pure JS"); - - // else implement in pure Javascript - Utils.loadData({ - object: "Collator", - locale: this.locale, - name: "collation.json", + var sync = true, + loadParams = undefined, + useNative = true; + + // defaults + /** + * @private + * @type {Locale} + */ + this.locale = new Locale(ilib.getLocale()); + + /** @private */ + this.caseFirst = "upper"; + /** @private */ + this.sensitivity = "variant"; + /** @private */ + this.level = 4; + /** @private */ + this.usage = "sort"; + /** @private */ + this.reverse = false; + /** @private */ + this.numeric = false; + /** @private */ + this.style = "default"; + /** @private */ + this.ignorePunctuation = false; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + if (options.sensitivity) { + switch (options.sensitivity) { + case 'primary': + case 'base': + this.sensitivity = "base"; + this.level = 1; + break; + case 'secondary': + case 'accent': + this.sensitivity = "accent"; + this.level = 2; + break; + case 'tertiary': + case 'case': + this.sensitivity = "case"; + this.level = 3; + break; + case 'quaternary': + case 'variant': + this.sensitivity = "variant"; + this.level = 4; + break; + } + } + if (typeof(options.upperFirst) !== 'undefined') { + this.caseFirst = options.upperFirst ? "upper" : "lower"; + } + + if (typeof(options.ignorePunctuation) !== 'undefined') { + this.ignorePunctuation = options.ignorePunctuation; + } + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + loadParams = options.loadParams; + if (typeof(options.useNative) !== 'undefined') { + useNative = options.useNative; + } + + if (options.usage === "sort" || options.usage === "search") { + this.usage = options.usage; + } + + if (typeof(options.reverse) === 'boolean') { + this.reverse = options.reverse; + } + + if (typeof(options.numeric) === 'boolean') { + this.numeric = options.numeric; + } + + if (typeof(options.style) === 'string') { + this.style = options.style; + } + } else { + options = {sync: true}; + } + + if (this.usage === "sort") { + // produces a stable sort + this.level = 4; + } + + if (useNative && typeof(Intl) !== 'undefined' && Intl) { + // this engine is modern and supports the new Intl object! + //console.log("implemented natively"); + /** + * @private + * @type {{compare:function(string,string)}} + */ + this.collator = new Intl.Collator(this.locale.getSpec(), { + sensitivity: this.sensitivity, + caseFirst: this.caseFirst, + ignorePunctuation: this.ignorePunctuation, + numeric: this.numeric, + usage: this.usage + }); + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } else { + //console.log("implemented in pure JS"); + + // else implement in pure Javascript + Utils.loadData({ + object: "Collator", + locale: this.locale, + name: "collation.json", replace: true, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (collation) { - if (!collation) { - collation = ilib.data.collation; - } - this._initCollation(collation); - if (this.ignorePunctuation) { - isPunct._init(sync, loadParams, ilib.bind(this, function() { - this._init(options); - })); - } else { - this._init(options); - } - }) - }); - } + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (collation) { + if (!collation) { + collation = ilib.data.collation; + } + this._initCollation(collation); + if (this.ignorePunctuation) { + isPunct._init(sync, loadParams, ilib.bind(this, function() { + this._init(options); + })); + } else { + this._init(options); + } + }) + }); + } }; Collator.prototype = { @@ -427,7 +427,7 @@ Collator.prototype = { */ _init: function(options) { if (this.numeric) { - // Create a fake INumber instance now to guarantee that the locale data + // Create a fake INumber instance now to guarantee that the locale data // is loaded so we can create sync INumber instances later, even in async mode new INumber("1", { sync: options.sync, @@ -444,350 +444,350 @@ Collator.prototype = { } } }, - - /** - * @private - * Bit pack an array of values into a single number - * @param {number|null|Array.} arr array of values to bit pack - * @param {number} offset offset for the start of this map - */ - _pack: function (arr, offset) { - var value = 0; - if (arr) { - if (typeof(arr) === 'number') { - arr = [ arr ]; - } - for (var i = 0; i < this.level; i++) { - var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); - if (i === 0) { - thisLevel += offset; - } - if (i > 0) { - value <<= this.collation.bits[i]; - } - if (i === 2 && this.caseFirst === "lower") { - // sort the lower case first instead of upper - value = value | (1 - thisLevel); - } else { - value = value | thisLevel; - } - } - } - return value; - }, - - /** - * @private - * Return the rule packed into an array of collation elements. - * @param {Array.>} rule - * @param {number} offset - * @return {Array.} a bit-packed array of numbers - */ - _packRule: function(rule, offset) { - if (ilib.isArray(rule[0])) { - var ret = []; - for (var i = 0; i < rule.length; i++) { - ret.push(this._pack(rule[i], offset)); - } - return ret; - } else { - return [ this._pack(rule, offset) ]; - } - }, - - /** - * @private - */ - _addChars: function (str, offset) { - var gs = new GlyphString(str); - var it = gs.charIterator(); - var c; - - while (it.hasNext()) { - c = it.next(); - if (c === "'") { - // escape a sequence of chars as one collation element - c = ""; - var x = ""; - while (it.hasNext() && x !== "'") { - c += x; - x = it.next(); - } - } - this.lastMap++; - this.map[c] = this._packRule([this.lastMap], offset); - } - }, - - /** - * @private - */ - _addRules: function(rules, start) { - var p; - for (var r in rules.map) { - if (r) { - this.map[r] = this._packRule(rules.map[r], start); - p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; - this.lastMap = Math.max(p + start, this.lastMap); - } - } - - if (typeof(rules.ranges) !== 'undefined') { - // for each range, everything in the range goes in primary sequence from the start - for (var i = 0; i < rules.ranges.length; i++) { - var range = rules.ranges[i]; - - this.lastMap = range.start; - if (typeof(range.chars) === "string") { - this._addChars(range.chars, start); - } else { - for (var k = 0; k < range.chars.length; k++) { - this._addChars(range.chars[k], start); - } - } - } - } - }, - - /** + + /** * @private + * Bit pack an array of values into a single number + * @param {number|null|Array.} arr array of values to bit pack + * @param {number} offset offset for the start of this map */ - _initCollation: function(rules) { - var rule = this.style; - while (typeof(rule) === 'string') { - rule = rules[rule]; - } - - if (!rule) { - rule = "default"; - - while (typeof(rule) === 'string') { - rule = rules[rule]; - } - } - if (!rule) { - this.map = {}; - return; - } - - /** - * @private - * @type {{scripts:Array.,bits:Array.,maxes:Array.,bases:Array.,map:Object.>>}} - */ - this.collation = rule; - this.map = {}; - this.lastMap = -1; - this.keysize = this.collation.keysize[this.level-1]; - this.defaultRule = rules["default"]; - - if (typeof(this.collation.inherit) !== 'undefined') { - for (var i = 0; i < this.collation.inherit.length; i++) { - if (this.collation.inherit === 'this') { - continue; - } - var col = this.collation.inherit[i]; - rule = typeof(col) === 'object' ? col.name : col; - if (rules[rule]) { - this._addRules(rules[rule], col.start || this.lastMap+1); - } - } - } - this._addRules(this.collation, this.lastMap+1); - }, - + _pack: function (arr, offset) { + var value = 0; + if (arr) { + if (typeof(arr) === 'number') { + arr = [ arr ]; + } + for (var i = 0; i < this.level; i++) { + var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); + if (i === 0) { + thisLevel += offset; + } + if (i > 0) { + value <<= this.collation.bits[i]; + } + if (i === 2 && this.caseFirst === "lower") { + // sort the lower case first instead of upper + value = value | (1 - thisLevel); + } else { + value = value | thisLevel; + } + } + } + return value; + }, + + /** + * @private + * Return the rule packed into an array of collation elements. + * @param {Array.>} rule + * @param {number} offset + * @return {Array.} a bit-packed array of numbers + */ + _packRule: function(rule, offset) { + if (ilib.isArray(rule[0])) { + var ret = []; + for (var i = 0; i < rule.length; i++) { + ret.push(this._pack(rule[i], offset)); + } + return ret; + } else { + return [ this._pack(rule, offset) ]; + } + }, + + /** + * @private + */ + _addChars: function (str, offset) { + var gs = new GlyphString(str); + var it = gs.charIterator(); + var c; + + while (it.hasNext()) { + c = it.next(); + if (c === "'") { + // escape a sequence of chars as one collation element + c = ""; + var x = ""; + while (it.hasNext() && x !== "'") { + c += x; + x = it.next(); + } + } + this.lastMap++; + this.map[c] = this._packRule([this.lastMap], offset); + } + }, + + /** + * @private + */ + _addRules: function(rules, start) { + var p; + for (var r in rules.map) { + if (r) { + this.map[r] = this._packRule(rules.map[r], start); + p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; + this.lastMap = Math.max(p + start, this.lastMap); + } + } + + if (typeof(rules.ranges) !== 'undefined') { + // for each range, everything in the range goes in primary sequence from the start + for (var i = 0; i < rules.ranges.length; i++) { + var range = rules.ranges[i]; + + this.lastMap = range.start; + if (typeof(range.chars) === "string") { + this._addChars(range.chars, start); + } else { + for (var k = 0; k < range.chars.length; k++) { + this._addChars(range.chars[k], start); + } + } + } + } + }, + + /** + * @private + */ + _initCollation: function(rules) { + var rule = this.style; + while (typeof(rule) === 'string') { + rule = rules[rule]; + } + + if (!rule) { + rule = "default"; + + while (typeof(rule) === 'string') { + rule = rules[rule]; + } + } + if (!rule) { + this.map = {}; + return; + } + + /** + * @private + * @type {{scripts:Array.,bits:Array.,maxes:Array.,bases:Array.,map:Object.>>}} + */ + this.collation = rule; + this.map = {}; + this.lastMap = -1; + this.keysize = this.collation.keysize[this.level-1]; + this.defaultRule = rules["default"]; + + if (typeof(this.collation.inherit) !== 'undefined') { + for (var i = 0; i < this.collation.inherit.length; i++) { + if (this.collation.inherit === 'this') { + continue; + } + var col = this.collation.inherit[i]; + rule = typeof(col) === 'object' ? col.name : col; + if (rules[rule]) { + this._addRules(rules[rule], col.start || this.lastMap+1); + } + } + } + this._addRules(this.collation, this.lastMap+1); + }, + /** * @private */ _basicCompare: function(left, right) { - var l = (left instanceof NormString) ? left : new NormString(left), - r = (right instanceof NormString) ? right : new NormString(right), - lelements, - relements, - diff; - - if (this.numeric) { - var lvalue = new INumber(left, {locale: this.locale}); - var rvalue = new INumber(right, {locale: this.locale}); - if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { - diff = lvalue.valueOf() - rvalue.valueOf(); - if (diff) { - return diff; - } else { - // skip the numeric part and compare the rest lexically - l = new NormString(left.substring(lvalue.parsed.length)); - r = new NormString(right.substring(rvalue.parsed.length)); - } - } - // else if they aren't both numbers, then let the code below take care of the lexical comparison instead - } - - lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); - relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); - - while (lelements.hasNext() && relements.hasNext()) { - diff = lelements.next() - relements.next(); - if (diff) { - return diff; - } - } - if (!lelements.hasNext() && !relements.hasNext()) { - return 0; - } else if (lelements.hasNext()) { - return 1; - } else { - return -1; - } + var l = (left instanceof NormString) ? left : new NormString(left), + r = (right instanceof NormString) ? right : new NormString(right), + lelements, + relements, + diff; + + if (this.numeric) { + var lvalue = new INumber(left, {locale: this.locale}); + var rvalue = new INumber(right, {locale: this.locale}); + if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { + diff = lvalue.valueOf() - rvalue.valueOf(); + if (diff) { + return diff; + } else { + // skip the numeric part and compare the rest lexically + l = new NormString(left.substring(lvalue.parsed.length)); + r = new NormString(right.substring(rvalue.parsed.length)); + } + } + // else if they aren't both numbers, then let the code below take care of the lexical comparison instead + } + + lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); + relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); + + while (lelements.hasNext() && relements.hasNext()) { + diff = lelements.next() - relements.next(); + if (diff) { + return diff; + } + } + if (!lelements.hasNext() && !relements.hasNext()) { + return 0; + } else if (lelements.hasNext()) { + return 1; + } else { + return -1; + } + }, + + /** + * Compare two strings together according to the rules of this + * collator instance. Do not use this function directly with + * Array.sort, as it will not have its collation data available + * and therefore will not function properly. Use the function + * returned by getComparator() instead. + * + * @param {string} left the left string to compare + * @param {string} right the right string to compare + * @return {number} a negative number if left comes before right, a + * positive number if right comes before left, and zero if left and + * right are equivalent according to this collator + */ + compare: function (left, right) { + // last resort: use the "C" locale + if (this.collator) { + // implemented by the core engine + return this.collator.compare(left, right); + } + + var ret = this._basicCompare(left, right); + return this.reverse ? -ret : ret; + }, + + /** + * Return a comparator function that can compare two strings together + * according to the rules of this collator instance. The function + * returns a negative number if the left + * string comes before right, a positive number if the right string comes + * before the left, and zero if left and right are equivalent. If the + * reverse property was given as true to the collator constructor, this + * function will + * switch the sign of those values to cause sorting to happen in the + * reverse order. + * + * @return {function(...)|undefined} a comparator function that + * can compare two strings together according to the rules of this + * collator instance + */ + getComparator: function() { + // bind the function to this instance so that we have the collation + // rules available to do the work + if (this.collator) { + // implemented by the core engine + return this.collator.compare; + } + + return ilib.bind(this, this.compare); }, - - /** - * Compare two strings together according to the rules of this - * collator instance. Do not use this function directly with - * Array.sort, as it will not have its collation data available - * and therefore will not function properly. Use the function - * returned by getComparator() instead. - * - * @param {string} left the left string to compare - * @param {string} right the right string to compare - * @return {number} a negative number if left comes before right, a - * positive number if right comes before left, and zero if left and - * right are equivalent according to this collator - */ - compare: function (left, right) { - // last resort: use the "C" locale - if (this.collator) { - // implemented by the core engine - return this.collator.compare(left, right); - } - - var ret = this._basicCompare(left, right); - return this.reverse ? -ret : ret; - }, - - /** - * Return a comparator function that can compare two strings together - * according to the rules of this collator instance. The function - * returns a negative number if the left - * string comes before right, a positive number if the right string comes - * before the left, and zero if left and right are equivalent. If the - * reverse property was given as true to the collator constructor, this - * function will - * switch the sign of those values to cause sorting to happen in the - * reverse order. - * - * @return {function(...)|undefined} a comparator function that - * can compare two strings together according to the rules of this - * collator instance - */ - getComparator: function() { - // bind the function to this instance so that we have the collation - // rules available to do the work - if (this.collator) { - // implemented by the core engine - return this.collator.compare; - } - - return ilib.bind(this, this.compare); - }, - - /** - * Return a sort key string for the given string. The sort key - * string is a list of values that represent each character - * in the original string. The sort key - * values for any particular character consists of 3 numbers that - * encode the primary, secondary, and tertiary characteristics - * of that character. The values of each characteristic are - * modified according to the strength of this collator instance - * to give the correct collation order. The idea is that this - * sort key string is directly comparable byte-for-byte to - * other sort key strings generated by this collator without - * any further knowledge of the collation rules for the locale. - * More formally, if a < b according to the rules of this collation, - * then it is guaranteed that sortkey(a) < sortkey(b) when compared - * byte-for-byte. The sort key string can therefore be used - * without the collator to sort an array of strings efficiently - * because the work of determining the applicability of various - * collation rules is done once up-front when generating - * the sort key.

- * - * The sort key string can be treated as a regular, albeit somewhat - * odd-looking, string. That is, it can be pass to regular - * Javascript functions without problems. - * - * @param {string} str the original string to generate the sort key for - * @return {string} a sort key string for the given string - */ - sortKey: function (str) { - if (!str) { - return ""; - } - - if (this.collator) { - // native, no sort keys available - return str; - } - - if (this.numeric) { - var v = new INumber(str, {locale: this.locale}); - var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); - return JSUtils.pad(s, 16); - } else { - var n = (typeof(str) === "string") ? new NormString(str) : str, - ret = "", - lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), - element; - - while (lelements.hasNext()) { - element = lelements.next(); - if (this.reverse) { - // for reverse, take the bitwise inverse - element = (1 << this.keysize) - element; - } - ret += JSUtils.pad(element.toString(16), this.keysize/4); - } - } - return ret; - } + + /** + * Return a sort key string for the given string. The sort key + * string is a list of values that represent each character + * in the original string. The sort key + * values for any particular character consists of 3 numbers that + * encode the primary, secondary, and tertiary characteristics + * of that character. The values of each characteristic are + * modified according to the strength of this collator instance + * to give the correct collation order. The idea is that this + * sort key string is directly comparable byte-for-byte to + * other sort key strings generated by this collator without + * any further knowledge of the collation rules for the locale. + * More formally, if a < b according to the rules of this collation, + * then it is guaranteed that sortkey(a) < sortkey(b) when compared + * byte-for-byte. The sort key string can therefore be used + * without the collator to sort an array of strings efficiently + * because the work of determining the applicability of various + * collation rules is done once up-front when generating + * the sort key.

+ * + * The sort key string can be treated as a regular, albeit somewhat + * odd-looking, string. That is, it can be pass to regular + * Javascript functions without problems. + * + * @param {string} str the original string to generate the sort key for + * @return {string} a sort key string for the given string + */ + sortKey: function (str) { + if (!str) { + return ""; + } + + if (this.collator) { + // native, no sort keys available + return str; + } + + if (this.numeric) { + var v = new INumber(str, {locale: this.locale}); + var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); + return JSUtils.pad(s, 16); + } else { + var n = (typeof(str) === "string") ? new NormString(str) : str, + ret = "", + lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), + element; + + while (lelements.hasNext()) { + element = lelements.next(); + if (this.reverse) { + // for reverse, take the bitwise inverse + element = (1 << this.keysize) - element; + } + ret += JSUtils.pad(element.toString(16), this.keysize/4); + } + } + return ret; + } }; /** - * Retrieve the list of collation style names that are available for the + * Retrieve the list of collation style names that are available for the * given locale. This list varies depending on the locale, and depending * on whether or not the data for that locale was assembled into this copy * of ilib. - * + * * @param {Locale|string=} locale The locale for which the available * styles are being sought * @return Array. an array of style names that are available for * the given locale */ Collator.getAvailableStyles = function (locale) { - return [ "standard" ]; + return [ "standard" ]; }; /** * Retrieve the list of ISO 15924 script codes that are available in this - * copy of ilib. This list varies depending on whether or not the data for + * copy of ilib. This list varies depending on whether or not the data for * various scripts was assembled into this copy of ilib. If the "ducet" * data is assembled into this copy of ilib, this method will report the * entire list of scripts as being available. If a collator instance is * instantiated with a script code that is not on the list returned by this * function, it will be ignored and text in that script will be sorted by * numeric Unicode values of the characters. - * - * @return Array. an array of ISO 15924 script codes that are + * + * @return Array. an array of ISO 15924 script codes that are * available */ Collator.getAvailableScripts = function () { - return [ "Latn" ]; + return [ "Latn" ]; }; /** * Return a default collation style - * + * * @returns {string} default collation style such as 'latin', 'korean' etc */ Collator.prototype.getDefaultCollatorStyle = function () { - return this.defaultRule; + return this.defaultRule; }; module.exports = Collator; diff --git a/js/lib/CopticCal.js b/js/lib/CopticCal.js index 217cb2c860..9d6e384826 100644 --- a/js/lib/CopticCal.js +++ b/js/lib/CopticCal.js @@ -1,6 +1,6 @@ /* * CopticCal.js - Represent a Coptic calendar object. - * + * * Copyright © 2015,2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,14 +25,14 @@ var EthiopicCal = require("./EthiopicCal.js"); * @class * Construct a new Coptic calendar object. This class encodes information about * a Coptic calendar.

- * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends EthiopicCal */ var CopticCal = function(options) { - this.type = "coptic"; - + this.type = "coptic"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } diff --git a/js/lib/CopticDate.js b/js/lib/CopticDate.js index 55763533af..396ec853b4 100644 --- a/js/lib/CopticDate.js +++ b/js/lib/CopticDate.js @@ -1,6 +1,6 @@ /* * CopticDate.js - Represent a date in the Coptic calendar - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var MathUtils = require("./MathUtils.js"); var Locale = require("./Locale.js"); @@ -31,46 +31,46 @@ var CopticRataDie = require("./CopticRataDie.js"); * @class * Construct a new date object for the Coptic Calendar. The constructor can be called * with a parameter object that contains any of the following properties: - * + * *

    - *
  • unixtime - sets the time of this instance according to the given + *
  • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). *
  • julianday - the Julian Day to set into this date *
  • year - any integer *
  • month - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene *
  • day - 1 to 30 - *
  • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + *
  • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation *
  • minute - 0 to 59 *
  • second - 0 to 59 *
  • millisecond - 0 to 999 - *
  • locale - the TimeZone instance or time zone name as a string + *
  • locale - the TimeZone instance or time zone name as a string * of this coptic date. The date/time is kept in the local time. The time zone * is used later if this date is formatted according to a different time zone and * the difference has to be calculated, or when the date format has a time zone * component in it. - *
  • timezone - the time zone of this instance. If the time zone is not + *
  • timezone - the time zone of this instance. If the time zone is not * given, it can be inferred from this locale. For locales that span multiple - * time zones, the one with the largest population is chosen as the one that - * represents the locale. - * + * time zones, the one with the largest population is chosen as the one that + * represents the locale. + * *
  • date - use the given intrinsic Javascript date to initialize this one. *
- * + * * If called with another Coptic date argument, the date components of the given * date are copied into the current one.

- * - * If the constructor is called with no arguments at all or if none of the - * properties listed above - * from unixtime through millisecond are present, then the date - * components are + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above + * from unixtime through millisecond are present, then the date + * components are * filled in with the current date at the time of instantiation. Note that if - * you do not give the time zone when defaulting to the current time and the + * you do not give the time zone when defaulting to the current time and the * time zone for all of ilib was not set with ilib.setTimeZone(), then the - * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich + * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich * Mean Time").

- * - * + * + * * @constructor * @extends EthiopicDate * @param {Object=} params parameters that govern the settings and behaviour of this Coptic date @@ -98,27 +98,27 @@ CopticDate.prototype.constructor = CopticDate; * @returns {RataDie} the new RD instance for the given params */ CopticDate.prototype.newRd = function (params) { - return new CopticRataDie(params); + return new CopticRataDie(params); }; /** * Return the day of the week of this date. The day of the week is encoded * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. - * + * * @return {number} the day of the week */ CopticDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd-3, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd-3, 7); }; /** * Return the name of the calendar that governs this date. - * + * * @return {string} a string giving the name of the calendar */ CopticDate.prototype.getCalendar = function() { - return "coptic"; + return "coptic"; }; //register with the factory method diff --git a/js/lib/CopticRataDie.js b/js/lib/CopticRataDie.js index c851ec4419..cb4f45edf9 100644 --- a/js/lib/CopticRataDie.js +++ b/js/lib/CopticRataDie.js @@ -1,6 +1,6 @@ /* * CopticRataDie.js - Represent an RD date in the Coptic calendar - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,69 +23,69 @@ var EthiopicRataDie = require("./EthiopicRataDie.js"); /** * @class - * Construct a new Coptic RD date number object. The constructor parameters can + * Construct a new Coptic RD date number object. The constructor parameters can * contain any of the following properties: - * + * *

    - *
  • unixtime - sets the time of this instance according to the given + *
  • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. - * + * *
  • julianday - sets the time of this instance according to the given * Julian Day instance or the Julian Day given as a float - * + * *
  • year - any integer, including 0 - * + * *
  • month - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene - * + * *
  • day - 1 to 30 - * - *
  • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + * + *
  • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation - * + * *
  • minute - 0 to 59 - * + * *
  • second - 0 to 59 - * + * *
  • millisecond - 0 to 999 - * + * *
  • date - use the given intrinsic Javascript date to initialize this one. *
* * If the constructor is called with another Coptic date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.

- * - * If the constructor is called with no arguments at all or if none of the - * properties listed above are present, then the RD is calculate based on + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above are present, then the RD is calculate based on * the current date at the time of instantiation.

- * + * * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).

- * - * + * + * * @private * @constructor * @extends EthiopicRataDie * @param {Object=} params parameters that govern the settings and behaviour of this Coptic RD date */ var CopticRataDie = function(params) { - this.cal = params && params.cal || new CopticCal(); - this.rd = NaN; - /** - * The difference between the zero Julian day and the first Coptic date - * of Friday, August 29, 284 CE Julian at 7:00am UTC. - * @private - * @type number - */ - this.epoch = 1825028.5; + this.cal = params && params.cal || new CopticCal(); + this.rd = NaN; + /** + * The difference between the zero Julian day and the first Coptic date + * of Friday, August 29, 284 CE Julian at 7:00am UTC. + * @private + * @type number + */ + this.epoch = 1825028.5; - var tmp = {}; - if (params) { - JSUtils.shallowCopy(params, tmp); - } - tmp.cal = this.cal; // override the cal parameter that may be passed in - EthiopicRataDie.call(this, tmp); + var tmp = {}; + if (params) { + JSUtils.shallowCopy(params, tmp); + } + tmp.cal = this.cal; // override the cal parameter that may be passed in + EthiopicRataDie.call(this, tmp); }; CopticRataDie.prototype = new EthiopicRataDie(); diff --git a/js/lib/Country.js b/js/lib/Country.js index 8af951d050..f41bd2c55d 100644 --- a/js/lib/Country.js +++ b/js/lib/Country.js @@ -19,7 +19,7 @@ // !data ctryreverse -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); @@ -61,57 +61,57 @@ var ResBundle = require("./ResBundle.js"); * @param options {Object} a set of properties to govern how this instance is constructed. */ var Country = function (options) { - var sync = true, - loadParams = undefined, - locale; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - this.locale = this.locale || new Locale(); - new LocaleInfo(this.locale, { - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (li) { - this.locinfo = li; - if (this.locinfo.getRegionName() === undefined) { - locale = 'en-US'; - } else { - locale = this.locale; - } - - if (!this.codeToCountry) { - Utils.loadData({ - name: "ctryreverse.json", - object: "Country", - locale: locale, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function(countries) { - this.codeToCountry = countries; - this._calculateCountryToCode(); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - this._calculateCountryToCode(); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } - }) - }); + var sync = true, + loadParams = undefined, + locale; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + this.locale = this.locale || new Locale(); + new LocaleInfo(this.locale, { + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (li) { + this.locinfo = li; + if (this.locinfo.getRegionName() === undefined) { + locale = 'en-US'; + } else { + locale = this.locale; + } + + if (!this.codeToCountry) { + Utils.loadData({ + name: "ctryreverse.json", + object: "Country", + locale: locale, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function(countries) { + this.codeToCountry = countries; + this._calculateCountryToCode(); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + this._calculateCountryToCode(); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } + }) + }); }; /** @@ -122,11 +122,11 @@ var Country = function (options) { * @return {Object} an object of country code that this copy of ilib knows about. */ Country.getAvailableCode = function() { - var countries = new ResBundle({ - name: "ctryreverse" - }).getResObj(); + var countries = new ResBundle({ + name: "ctryreverse" + }).getResObj(); - return countries && Object.keys(countries); + return countries && Object.keys(countries); }; /** @@ -136,81 +136,81 @@ Country.getAvailableCode = function() { * @return {Object} an object of country code that this copy of ilib knows about. */ Country.getAvailableCountry = function() { - var ret = [], - code, - countries = new ResBundle({ - name: "ctryreverse" - }).getResObj(); - - for (code in countries) { - if (code && countries[code]) { - ret.push(countries[code]); - } - } - - return ret; + var ret = [], + code, + countries = new ResBundle({ + name: "ctryreverse" + }).getResObj(); + + for (code in countries) { + if (code && countries[code]) { + ret.push(countries[code]); + } + } + + return ret; }; Country.prototype = { - /** - * @private - */ - _calculateCountryToCode: function() { - var temp = this.codeToCountry, - code; - - this.countryToCode = {}; - - for (code in temp) { - if (code && temp[code]) { - this.countryToCode[temp[code]] = code; - } - } - }, - - /** - * Return the country code corresponding to the country name given. - * If the country name is given, but it is not found in the list of known countries, this - * method will throw an exception. - * @param {string} ctryname The country name in the language of the locale of this instance - * @return {string} the country code corresponding to the country name - * @throws "Country xx is unknown" when the given country name is not in the list of - * known country names. xx is replaced with the requested country name. - */ - getCode: function (ctryname) { - if (!this.countryToCode[ctryname]) { - throw "Country " + ctryname + " is unknown"; - } - return this.countryToCode[ctryname]; - }, - - /** - * Return the country name corresponding to the country code given. - * If the code is given, but it is not found in the list of known countries, this - * method will throw an exception. - * @param {string} code The country code to get the country name - * @return {string} the country name in the language of the locale of this instance - * @throws "Country xx is unknown" when the given country code is not in the list of - * known country codes. xx is replaced with the requested country code. - */ - getName: function (code) { - if (!this.codeToCountry[code]) { - throw "Country " + code + " is unknown"; - } - return this.codeToCountry[code]; - }, - - /** - * Return the locale for this country. If the options to the constructor - * included a locale property in order to find the country that is appropriate - * for that locale, then the locale is returned here. If the options did not - * include a locale, then this method returns undefined. - * @return {Locale} the locale used in the constructor of this instance, - * or undefined if no locale was given in the constructor - */ - getLocale: function () { - return this.locale; - } + /** + * @private + */ + _calculateCountryToCode: function() { + var temp = this.codeToCountry, + code; + + this.countryToCode = {}; + + for (code in temp) { + if (code && temp[code]) { + this.countryToCode[temp[code]] = code; + } + } + }, + + /** + * Return the country code corresponding to the country name given. + * If the country name is given, but it is not found in the list of known countries, this + * method will throw an exception. + * @param {string} ctryname The country name in the language of the locale of this instance + * @return {string} the country code corresponding to the country name + * @throws "Country xx is unknown" when the given country name is not in the list of + * known country names. xx is replaced with the requested country name. + */ + getCode: function (ctryname) { + if (!this.countryToCode[ctryname]) { + throw "Country " + ctryname + " is unknown"; + } + return this.countryToCode[ctryname]; + }, + + /** + * Return the country name corresponding to the country code given. + * If the code is given, but it is not found in the list of known countries, this + * method will throw an exception. + * @param {string} code The country code to get the country name + * @return {string} the country name in the language of the locale of this instance + * @throws "Country xx is unknown" when the given country code is not in the list of + * known country codes. xx is replaced with the requested country code. + */ + getName: function (code) { + if (!this.codeToCountry[code]) { + throw "Country " + code + " is unknown"; + } + return this.codeToCountry[code]; + }, + + /** + * Return the locale for this country. If the options to the constructor + * included a locale property in order to find the country that is appropriate + * for that locale, then the locale is returned here. If the options did not + * include a locale, then this method returns undefined. + * @return {Locale} the locale used in the constructor of this instance, + * or undefined if no locale was given in the constructor + */ + getLocale: function () { + return this.locale; + } }; module.exports = Country; diff --git a/js/lib/Currency.js b/js/lib/Currency.js index 5771c9fb62..3d417a4eb6 100644 --- a/js/lib/Currency.js +++ b/js/lib/Currency.js @@ -1,6 +1,6 @@ /* * Currency.js - Currency definition - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data currency -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); @@ -27,43 +27,43 @@ var ResBundle = require("./ResBundle.js"); /** * @class - * Create a new currency information instance. Instances of this class encode + * Create a new currency information instance. Instances of this class encode * information about a particular currency.

- * + * * Note: that if you are looking to format currency for display, please see * the number formatting class {NumFmt}. This class only gives information - * about currencies.

- * + * about currencies.

+ * * The options can contain any of the following properties: - * + * *

    *
  • locale - specify the locale for this instance - *
  • code - find info on a specific currency with the given ISO 4217 code + *
  • code - find info on a specific currency with the given ISO 4217 code *
  • sign - search for a currency that uses this sign - *
  • onLoad - a callback function to call when the currency data is fully + *
  • onLoad - a callback function to call when the currency data is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - *
  • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * When searching for a currency by its sign, this class cannot guarantee - * that it will return info about a specific currency. The reason is that currency - * signs are sometimes shared between different currencies and the sign is - * therefore ambiguous. If you need a + * + * When searching for a currency by its sign, this class cannot guarantee + * that it will return info about a specific currency. The reason is that currency + * signs are sometimes shared between different currencies and the sign is + * therefore ambiguous. If you need a * guarantee, find the currency using the code instead.

- * - * The way this class finds a currency by sign is the following. If the sign is + * + * The way this class finds a currency by sign is the following. If the sign is * unambiguous, then * the currency is returned. If there are multiple currencies that use the same * sign, and the current locale uses that sign, then the default currency for @@ -71,11 +71,11 @@ var ResBundle = require("./ResBundle.js"); * does not use that sign, then the currency with the largest circulation is * returned. For example, if you are in the en-GB locale, and the sign is "$", * then this class will notice that there are multiple currencies with that - * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will + * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will * pick the one with the largest circulation, which in this case is the US Dollar * (USD).

- * - * If neither the code or sign property is set, the currency that is most common + * + * If neither the code or sign property is set, the currency that is most common * for the locale * will be used instead. If the locale is not set, the default locale will be used. * If the code is given, but it is not found in the list of known currencies, this @@ -84,183 +84,183 @@ var ResBundle = require("./ResBundle.js"); * the code and sign properties are given, then the sign property will be ignored * and only the code property used. If the locale is given, but it is not a known * locale, this class will default to the default locale instead.

- * - * + * + * * @constructor * @param options {Object} a set of properties to govern how this instance is constructed. - * @throws "currency xxx is unknown" when the given currency code is not in the list of + * @throws "currency xxx is unknown" when the given currency code is not in the list of * known currencies. xxx is replaced with the requested code. */ var Currency = function (options) { - if (options) { - if (options.code) { - this.code = options.code; - } - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - if (options.sign) { - this.sign = options.sign; - } - if (options.loadParams) { - this.loadParams = options.loadParams; - } - } else { - options = {sync: true}; - } - + if (options) { + if (options.code) { + this.code = options.code; + } + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + if (options.sign) { + this.sign = options.sign; + } + if (options.loadParams) { + this.loadParams = options.loadParams; + } + } else { + options = {sync: true}; + } + if (typeof(options.sync) === 'undefined') { options.sync = true; } - - this.locale = this.locale || new Locale(); - if (typeof(ilib.data.currency) === 'undefined') { - Utils.loadData({ - name: "currency.json", - object: "Currency", - locale: "-", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function(currency) { - ilib.data.currency = currency; - this._loadLocinfo(options); - }) - }); - } else { - this._loadLocinfo(options); - } + + this.locale = this.locale || new Locale(); + if (typeof(ilib.data.currency) === 'undefined') { + Utils.loadData({ + name: "currency.json", + object: "Currency", + locale: "-", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function(currency) { + ilib.data.currency = currency; + this._loadLocinfo(options); + }) + }); + } else { + this._loadLocinfo(options); + } }; /** * Return an array of the ids for all ISO 4217 currencies that * this copy of ilib knows about. - * + * * @static * @return {Array.} an array of currency ids that this copy of ilib knows about. */ Currency.getAvailableCurrencies = function() { - var ret = [], - cur, - currencies = new ResBundle({ - name: "currency" - }).getResObj(); - - for (cur in currencies) { - if (cur && currencies[cur]) { - ret.push(cur); - } - } - - return ret; + var ret = [], + cur, + currencies = new ResBundle({ + name: "currency" + }).getResObj(); + + for (cur in currencies) { + if (cur && currencies[cur]) { + ret.push(cur); + } + } + + return ret; }; Currency.prototype = { - /** - * @private - */ - _loadLocinfo: function(options) { - new LocaleInfo(this.locale, { - sync: options.sync, - loadParams: options.loadParams, - onLoad: ilib.bind(this, function (li) { - var currInfo; + /** + * @private + */ + _loadLocinfo: function(options) { + new LocaleInfo(this.locale, { + sync: options.sync, + loadParams: options.loadParams, + onLoad: ilib.bind(this, function (li) { + var currInfo; + + this.locinfo = li; + if (this.code) { + currInfo = ilib.data.currency[this.code]; + if (!currInfo) { + if (options.sync) { + throw "currency " + this.code + " is unknown"; + } else if (typeof(options.onLoad) === "function") { + options.onLoad(undefined); + return; + } + } + } else if (this.sign) { + currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... + if (typeof(currInfo) !== 'undefined') { + this.code = this.sign; + } else { + this.code = this.locinfo.getCurrency(); + currInfo = ilib.data.currency[this.code]; + if (currInfo.sign !== this.sign) { + // current locale does not use the sign, so search for it + for (var cur in ilib.data.currency) { + if (cur && ilib.data.currency[cur]) { + currInfo = ilib.data.currency[cur]; + if (currInfo.sign === this.sign) { + // currency data is already ordered so that the currency with the + // largest circulation is at the beginning, so all we have to do + // is take the first one in the list that matches + this.code = cur; + break; + } + } + } + } + } + } - this.locinfo = li; - if (this.code) { - currInfo = ilib.data.currency[this.code]; - if (!currInfo) { - if (options.sync) { - throw "currency " + this.code + " is unknown"; - } else if (typeof(options.onLoad) === "function") { - options.onLoad(undefined); - return; - } - } - } else if (this.sign) { - currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... - if (typeof(currInfo) !== 'undefined') { - this.code = this.sign; - } else { - this.code = this.locinfo.getCurrency(); - currInfo = ilib.data.currency[this.code]; - if (currInfo.sign !== this.sign) { - // current locale does not use the sign, so search for it - for (var cur in ilib.data.currency) { - if (cur && ilib.data.currency[cur]) { - currInfo = ilib.data.currency[cur]; - if (currInfo.sign === this.sign) { - // currency data is already ordered so that the currency with the - // largest circulation is at the beginning, so all we have to do - // is take the first one in the list that matches - this.code = cur; - break; - } - } - } - } - } - } + if (!currInfo || !this.code) { + this.code = this.locinfo.getCurrency(); + currInfo = ilib.data.currency[this.code]; + } - if (!currInfo || !this.code) { - this.code = this.locinfo.getCurrency(); - currInfo = ilib.data.currency[this.code]; - } + this.name = currInfo.name; + this.fractionDigits = currInfo.decimals; + this.sign = currInfo.sign; - this.name = currInfo.name; - this.fractionDigits = currInfo.decimals; - this.sign = currInfo.sign; + if (typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + }, - if (typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - }, - - /** - * Return the ISO 4217 currency code for this instance. - * @return {string} the ISO 4217 currency code for this instance - */ - getCode: function () { - return this.code; - }, - - /** - * Return the default number of fraction digits that is typically used - * with this type of currency. - * @return {number} the number of fraction digits for this currency - */ - getFractionDigits: function () { - return this.fractionDigits; - }, - - /** - * Return the sign commonly used to represent this currency. - * @return {string} the sign commonly used to represent this currency - */ - getSign: function () { - return this.sign; - }, - - /** - * Return the name of the currency in English. - * @return {string} the name of the currency in English - */ - getName: function () { - return this.name; - }, - - /** - * Return the locale for this currency. If the options to the constructor - * included a locale property in order to find the currency that is appropriate - * for that locale, then the locale is returned here. If the options did not - * include a locale, then this method returns undefined. - * @return {Locale} the locale used in the constructor of this instance, - * or undefined if no locale was given in the constructor - */ - getLocale: function () { - return this.locale; - } + /** + * Return the ISO 4217 currency code for this instance. + * @return {string} the ISO 4217 currency code for this instance + */ + getCode: function () { + return this.code; + }, + + /** + * Return the default number of fraction digits that is typically used + * with this type of currency. + * @return {number} the number of fraction digits for this currency + */ + getFractionDigits: function () { + return this.fractionDigits; + }, + + /** + * Return the sign commonly used to represent this currency. + * @return {string} the sign commonly used to represent this currency + */ + getSign: function () { + return this.sign; + }, + + /** + * Return the name of the currency in English. + * @return {string} the name of the currency in English + */ + getName: function () { + return this.name; + }, + + /** + * Return the locale for this currency. If the options to the constructor + * included a locale property in order to find the currency that is appropriate + * for that locale, then the locale is returned here. If the options did not + * include a locale, then this method returns undefined. + * @return {Locale} the locale used in the constructor of this instance, + * or undefined if no locale was given in the constructor + */ + getLocale: function () { + return this.locale; + } }; module.exports = Currency; diff --git a/js/lib/DateFactory.js b/js/lib/DateFactory.js index 011ee4b4e5..1786211ee8 100644 --- a/js/lib/DateFactory.js +++ b/js/lib/DateFactory.js @@ -1,7 +1,7 @@ /* - * DateFactory.js - Factory class to create the right subclasses of a date for any + * DateFactory.js - Factory class to create the right subclasses of a date for any * calendar or locale. - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); @@ -32,111 +32,111 @@ var IDate = require("./IDate.js"); /** * Factory method to create a new instance of a date subclass.

- * + * * The options parameter can be an object that contains the following * properties: - * + * *

    *
  • type - specify the type/calendar of the date desired. The - * list of valid values changes depending on which calendars are - * defined. When assembling your iliball.js, include those date type - * you wish to use in your program or web page, and they will register + * list of valid values changes depending on which calendars are + * defined. When assembling your iliball.js, include those date type + * you wish to use in your program or web page, and they will register * themselves with this factory method. The "gregorian", * and "julian" calendars are all included by default, as they are the * standard calendars for much of the world. If not specified, the type * of the date returned is the one that is appropriate for the locale. * This property may also be given as "calendar" instead of "type". - * - *
  • onLoad - a callback function to call when the date object is fully + * + *
  • onLoad - a callback function to call when the date object is fully * loaded. When the onLoad option is given, the date factory will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
  • loadParams - an object containing parameters to pass to the + * + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * The options object is also passed down to the date constructor, and + * + * The options object is also passed down to the date constructor, and * thus can contain the the properties as the date object being instantiated. * See the documentation for {@link GregorianDate}, and other * subclasses for more details on other parameter that may be passed in.

- * + * * Please note that if you do not give the type parameter, this factory * method will create a date object that is appropriate for the calendar - * that is most commonly used in the specified or current ilib locale. - * For example, in Thailand, the most common calendar is the Thai solar - * calendar. If the current locale is "th-TH" (Thai for Thailand) and you + * that is most commonly used in the specified or current ilib locale. + * For example, in Thailand, the most common calendar is the Thai solar + * calendar. If the current locale is "th-TH" (Thai for Thailand) and you * use this factory method to construct a new date without specifying the - * type, it will automatically give you back an instance of - * {@link ThaiSolarDate}. This is convenient because you do not - * need to know which locales use which types of dates. In fact, you + * type, it will automatically give you back an instance of + * {@link ThaiSolarDate}. This is convenient because you do not + * need to know which locales use which types of dates. In fact, you * should always use this factory method to make new date instances unless * you know that you specifically need a date in a particular calendar.

- * + * * Also note that when you pass in the date components such as year, month, * day, etc., these components should be appropriate for the given date * being instantiated. That is, in our Thai example in the previous * paragraph, the year and such should be given as a Thai solar year, not * the Gregorian year that you get from the Javascript Date class. In * order to initialize a date instance when you don't know what subclass - * will be instantiated for the locale, use a parameter such as "unixtime" + * will be instantiated for the locale, use a parameter such as "unixtime" * or "julianday" which are unambiguous and based on UTC time, instead of - * the year/month/date date components. The date components for that UTC - * time will be calculated and the time zone offset will be automatically + * the year/month/date date components. The date components for that UTC + * time will be calculated and the time zone offset will be automatically * factored in. - * + * * @static * @param {Object=} options options controlling the construction of this instance, or * undefined to use the default options - * @return {IDate} an instance of a calendar object of the appropriate type + * @return {IDate} an instance of a calendar object of the appropriate type */ var DateFactory = function(options) { - var locale, - type, - sync = true, - obj; + var locale, + type, + sync = true, + obj; + + if (options) { + if (options.locale) { + locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } - if (options) { - if (options.locale) { - locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - type = options.type || options.calendar; - - if (typeof(options.sync) === 'boolean') { - sync = options.sync; - } - } - - if (!locale) { - locale = new Locale(); // default locale - } + type = options.type || options.calendar; - if (!type) { - new LocaleInfo(locale, { - sync: sync, - loadParams: options && options.loadParams, - onLoad: function(info) { - type = info.getCalendar(); - - obj = DateFactory._init(type, options); - } - }); - } else { - obj = DateFactory._init(type, options); - } - - return obj + if (typeof(options.sync) === 'boolean') { + sync = options.sync; + } + } + + if (!locale) { + locale = new Locale(); // default locale + } + + if (!type) { + new LocaleInfo(locale, { + sync: sync, + loadParams: options && options.loadParams, + onLoad: function(info) { + type = info.getCalendar(); + + obj = DateFactory._init(type, options); + } + }); + } else { + obj = DateFactory._init(type, options); + } + + return obj }; /** @@ -145,24 +145,24 @@ var DateFactory = function(options) { * @private */ DateFactory._dynMap = { - "coptic": "Coptic", - "ethiopic": "Ethiopic", - "gregorian": "Gregorian", - "han": "Han", - "hebrew": "Hebrew", - "islamic": "Islamic", - "julian": "Julian", - "persian": "Persian", - "persian-algo": "PersianAlgo", - "thaisolar": "ThaiSolar" + "coptic": "Coptic", + "ethiopic": "Ethiopic", + "gregorian": "Gregorian", + "han": "Han", + "hebrew": "Hebrew", + "islamic": "Islamic", + "julian": "Julian", + "persian": "Persian", + "persian-algo": "PersianAlgo", + "thaisolar": "ThaiSolar" }; function circumventWebPackDate(x) { - return "./" + x + "Date.js"; + return "./" + x + "Date.js"; } function circumventWebPackCal(x) { - return "./" + x + "Cal.js"; + return "./" + x + "Cal.js"; } /** @@ -170,35 +170,35 @@ function circumventWebPackCal(x) { * @protected */ DateFactory._dynLoadDate = function (name, fnc) { - if (!IDate._constructors[name]) { - var entry = DateFactory._dynMap[name]; - if (entry) { - IDate._constructors[name] = require(fnc(entry)); - } - } - return IDate._constructors[name]; + if (!IDate._constructors[name]) { + var entry = DateFactory._dynMap[name]; + if (entry) { + IDate._constructors[name] = require(fnc(entry)); + } + } + return IDate._constructors[name]; }; -/** +/** * @protected - * @static + * @static */ DateFactory._init = function(type, options) { - var cons; - - if (ilib.isDynCode()) { - DateFactory._dynLoadDate(type, circumventWebPackDate); - CalendarFactory._dynLoadCalendar(type, circumventWebPackCal); - } - - cons = IDate._constructors[type]; - - // pass the same options through to the constructor so the subclass - // has the ability to do something with if it needs to - if (!cons && typeof(options.onLoad) === "function") { - options.onLoad(undefined); - } - return cons && new cons(options); + var cons; + + if (ilib.isDynCode()) { + DateFactory._dynLoadDate(type, circumventWebPackDate); + CalendarFactory._dynLoadCalendar(type, circumventWebPackCal); + } + + cons = IDate._constructors[type]; + + // pass the same options through to the constructor so the subclass + // has the ability to do something with if it needs to + if (!cons && options && typeof(options.onLoad) === "function") { + options.onLoad(undefined); + } + return cons && new cons(options); }; /** @@ -206,10 +206,10 @@ DateFactory._init = function(type, options) { * string or number that can be translated by the JavaScript Date class, * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object - * containing the normal options to initialize an IDate instance, or null (will - * return null or undefined if input is null or undefined). Normal output is + * containing the normal options to initialize an IDate instance, or null (will + * return null or undefined if input is null or undefined). Normal output is * a standard native subclass of the IDate object as appropriate for the locale. - * + * * @static * @protected * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. @@ -218,44 +218,44 @@ DateFactory._init = function(type, options) { * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate */ DateFactory._dateToIlib = function(inDate, timezone, locale) { - if (typeof(inDate) === 'undefined' || inDate === null) { - return inDate; - } - if (inDate instanceof IDate) { - return inDate; - } - if (typeof(inDate) === 'number') { - return DateFactory({ - unixtime: inDate, - timezone: timezone, - locale: locale - }); - } - if (typeof(inDate) === 'string') { - inDate = new Date(inDate); - } - if (JSUtils.isDate(inDate)) { - return DateFactory({ - unixtime: inDate.getTime(), - timezone: timezone, - locale: locale - }); - } - if (inDate instanceof JulianDay) { - return DateFactory({ - jd: inDate, - timezone: timezone, - locale: locale - }); - } - if (typeof(inDate) === 'object') { - return DateFactory(inDate); - } - return DateFactory({ - unixtime: inDate.getTime(), - timezone: timezone, - locale: locale - }); + if (typeof(inDate) === 'undefined' || inDate === null) { + return inDate; + } + if (inDate instanceof IDate) { + return inDate; + } + if (typeof(inDate) === 'number') { + return DateFactory({ + unixtime: inDate, + timezone: timezone, + locale: locale + }); + } + if (typeof(inDate) === 'string') { + inDate = new Date(inDate); + } + if (JSUtils.isDate(inDate)) { + return DateFactory({ + unixtime: inDate.getTime(), + timezone: timezone, + locale: locale + }); + } + if (inDate instanceof JulianDay) { + return DateFactory({ + jd: inDate, + timezone: timezone, + locale: locale + }); + } + if (typeof(inDate) === 'object') { + return DateFactory(inDate); + } + return DateFactory({ + unixtime: inDate.getTime(), + timezone: timezone, + locale: locale + }); }; module.exports = DateFactory; diff --git a/js/lib/DateFmt.js b/js/lib/DateFmt.js index 3f9c0186ed..2e318c7dae 100644 --- a/js/lib/DateFmt.js +++ b/js/lib/DateFmt.js @@ -1,6 +1,6 @@ /* * DateFmt.js - Date formatter definition - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data dateformats sysres -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -43,77 +43,77 @@ var ISet = require("./ISet.js"); * options. Create different date formatter instances for different purposes * and then keep them cached for use later if you have more than one date to * format.

- * + * * The options may contain any of the following properties: - * + * *

    *
  • locale - locale to use when formatting the date/time. If the locale is * not specified, then the default locale of the app or web page will be used. - * + * *
  • calendar - the type of calendar to use for this format. The value should * be a sting containing the name of the calendar. Currently, the supported * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the * calendar is not specified, then the default calendar for the locale is used. When the * calendar type is specified, then the format method must be called with an instance of - * the appropriate date type. (eg. Gregorian calendar means that the format method must + * the appropriate date type. (eg. Gregorian calendar means that the format method must * be called with a GregDate instance.) - * + * *
  • timezone - time zone to use when formatting times. This may be a time zone - * instance or a time zone specifier from the IANA list of time zone database names - * (eg. "America/Los_Angeles"), + * instance or a time zone specifier from the IANA list of time zone database names + * (eg. "America/Los_Angeles"), * the string "local", or a string specifying the offset in RFC 822 format. The IANA - * list of time zone names can be viewed at + * list of time zone names can be viewed at * this page. * If the time zone is given as "local", the offset from UTC as given by * the Javascript system is used. If the offset is given as an RFC 822 style offset * specifier, it will parse that string and use the resulting offset. If the time zone * is not specified, the * default time zone for the locale is used. If both the date object and this formatter - * instance contain time zones and those time zones are different from each other, the - * formatter will calculate the offset between the time zones and subtract it from the + * instance contain time zones and those time zones are different from each other, the + * formatter will calculate the offset between the time zones and subtract it from the * date before formatting the result for the current time zone. The theory is that a date * object that contains a time zone specifies a specific instant in time that is valid * around the world, whereas a date object without one is a local time and can only be * used for doing things in the local time zone of the user. - * + * *
  • type - Specify whether this formatter should format times only, dates only, or * both times and dates together. Valid values are "time", "date", and "datetime". Note that - * in some locales, the standard format uses the order "time followed by date" and in others, - * the order is exactly opposite, so it is better to create a single "datetime" formatter - * than it is to create a time formatter and a date formatter separately and concatenate the + * in some locales, the standard format uses the order "time followed by date" and in others, + * the order is exactly opposite, so it is better to create a single "datetime" formatter + * than it is to create a time formatter and a date formatter separately and concatenate the * results. A "datetime" formatter will get the order correct for the locale.

    - * + * * The default type if none is specified in with the type option is "date". - * - *

  • length - Specify the length of the format to use. The length is the approximate size of the + * + *
  • length - Specify the length of the format to use. The length is the approximate size of the * formatted string. - * + * *
      *
    • short - use a short representation of the time. This is the most compact format possible for the locale. *
    • medium - use a medium length representation of the time. This is a slightly longer format. - *
    • long - use a long representation of the time. This is a fully specified format, but some of the textual + *
    • long - use a long representation of the time. This is a fully specified format, but some of the textual * components may still be abbreviated - *
    • full - use a full representation of the time. This is a fully specified format where all the textual + *
    • full - use a full representation of the time. This is a fully specified format where all the textual * components are spelled out completely *
    - * + * * eg. The "short" format for an en_US date may be "MM/dd/yy", whereas the long format might be "d MMM, yyyy". In the long - * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format + * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format * contains slightly more spaces and formatting characters.

    - * + * * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" * properties to specify the components. Also, very few of the components of a time format differ according to the length, * so this property has little to no affect on time formatting. - * + * *

  • date - This property tells * which components of a date format to use. For example, * sometimes you may wish to format a date that only contains the month and date * without the year, such as when displaying a person's yearly birthday. The value * of this property allows you to specify only those components you want to see in the * final output, ordered correctly for the locale.

    - * + * * Valid values are: - * + * *

      *
    • dmwy - format all components, weekday, date, month, and year *
    • dmy - format only date, month, and year @@ -122,26 +122,26 @@ var ISet = require("./ISet.js"); *
    • my - format only month and year *
    • dw - format only the weekday and date *
    • d - format only the date - *
    • m - format only the month, in numbers for shorter lengths, and letters for + *
    • m - format only the month, in numbers for shorter lengths, and letters for * longer lengths *
    • n - format only the month, in letters only for all lengths *
    • y - format only the year *
    * Default components, if this property is not specified, is "dmy". This property may be specified * but has no affect if the current formatter is for times only.

    - * - * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you - * get from DateTimePatternGenerator.getSkeleton(). - * It will not extract the length from the skeleton so you still need to pass the length property, + * + * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you + * get from DateTimePatternGenerator.getSkeleton(). + * It will not extract the length from the skeleton so you still need to pass the length property, * but it will extract the date components. - * - *

  • time - This property gives which components of a time format to use. The time will be formatted - * correctly for the locale with only the time components requested. For example, a clock might only display - * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set + * + *
  • time - This property gives which components of a time format to use. The time will be formatted + * correctly for the locale with only the time components requested. For example, a clock might only display + * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set * to "hm".

    - * + * * Valid values for this property are: - * + * *

      *
    • ahmsz - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone *
    • ahms - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) @@ -157,37 +157,37 @@ var ISet = require("./ISet.js"); *
    • m - format only the minutes *
    • s - format only the seconds *
    - * + * * If you want to format a length of time instead of a particular instant * in time, use the duration formatter object (DurationFmt) instead because this - * formatter is geared towards instants. A date formatter will make sure that each component of the + * formatter is geared towards instants. A date formatter will make sure that each component of the * time is within the normal range * for that component. That is, the minutes will always be between 0 and 59, no matter * what is specified in the date to format. A duration format will allow the number * of minutes to exceed 59 if, for example, you were displaying the length of * a movie of 198 minutes.

    - * + * * Default value if this property is not specified is "hma".

    - * - * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you - * get from DateTimePatternGenerator.getSkeleton(). - * It will not extract the length from the skeleton so you still need to pass the length property, + * + * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you + * get from DateTimePatternGenerator.getSkeleton(). + * It will not extract the length from the skeleton so you still need to pass the length property, * but it will extract the time components. - * - *

  • clock - specify that the time formatter should use a 12 or 24 hour clock. + * + *
  • clock - specify that the time formatter should use a 12 or 24 hour clock. * Valid values are "12" and "24".

    - * + * * In some locales, both clocks are used. For example, in en_US, the general populace uses - * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or + * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or * scientific writing, it is more common to use a 24 hour clock. This property allows you to * construct a formatter that overrides the default for the locale.

    - * + * * If this property is not specified, the default is to use the most widely used convention * for the locale. - * - *

  • template - use the given template string as a fixed format when formatting + * + *
  • template - use the given template string as a fixed format when formatting * the date/time. Valid codes to use in a template string are as follows: - * + * *
      *
    • a - am/pm marker *
    • d - 1 or 2 digit date of month, not padded @@ -207,7 +207,7 @@ var ISet = require("./ISet.js"); *
    • E - day-of-week name, abbreviated to a single character *
    • EE - day-of-week name, abbreviated to a max of 2 characters *
    • EEE - day-of-week name, abbreviated to a max of 3 characters - *
    • EEEE - day-of-week name fully spelled out + *
    • EEEE - day-of-week name fully spelled out *
    • G - era designator *
    • w - week number in year *
    • ww - week number in year, 0 padded to 2 digits @@ -229,310 +229,310 @@ var ISet = require("./ISet.js"); *
    • z - general time zone *
    • Z - RFC 822 time zone *
    - * - *
  • useNative - the flag used to determine whether to use the native script settings + * + *
  • useNative - the flag used to determine whether to use the native script settings * for formatting the numbers. * - *
  • meridiems - string that specifies what style of meridiems to use with this - * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" - * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. + *
  • meridiems - string that specifies what style of meridiems to use with this + * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" + * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. * (For almost all locales, the Gregorian AM/PM style is most frequently used.) - * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", - * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding + * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", + * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" * when formatting dates in the Gregorian calendar. * - *
  • onLoad - a callback function to call when the date format object is fully + *
  • onLoad - a callback function to call when the date format object is fully * loaded. When the onLoad option is given, the DateFmt object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
  • loadParams - an object containing parameters to pass to the + * + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
- * - * Any substring containing letters within single or double quotes will be used + * + * Any substring containing letters within single or double quotes will be used * as-is in the final output and will not be interpretted for codes as above.

- * + * * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where - * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical + * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical * output for this example template might be, "El 5. de Mayo". - * + * * The following options will be used when formatting a date/time with an explicit * template: - * + * *

    - *
  • locale - the locale is only used for + *
  • locale - the locale is only used for * translations of things like month names or day-of-week names. - *
  • calendar - used to translate a date instance into date/time component values + *
  • calendar - used to translate a date instance into date/time component values * that can be formatted into the template *
  • timezone - used to figure out the offset to add or subtract from the time to * get the final time component values *
  • clock - used to figure out whether to format times with a 12 or 24 hour clock. * If this option is specified, it will override the hours portion of a time format. - * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. - * If this option is not specified, the 12/24 code in the template will dictate whether + * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. + * If this option is not specified, the 12/24 code in the template will dictate whether * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. *
- * + * * All other options will be ignored and their corresponding getter methods will * return the empty string.

- * - * + * + * * @constructor * @param {Object} options options governing the way this date formatter instance works */ var DateFmt = function(options) { - var arr, i, bad, c, comps, - sync = true, - loadParams = undefined; - - this.locale = new Locale(); - this.type = "date"; - this.length = "s"; - this.dateComponents = "dmy"; - this.timeComponents = "ahm"; - this.meridiems = "default"; - - options = options || {sync: true}; - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.type) { - if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { - this.type = options.type; - } - } - - if (options.calendar) { - this.calName = options.calendar; - } - - if (options.length) { - if (options.length === 'short' || - options.length === 'medium' || - options.length === 'long' || - options.length === 'full') { - // only use the first char to save space in the json files - this.length = options.length.charAt(0); - } - } - - if (options.date) { - arr = options.date.split(""); - var dateComps = new ISet(); - bad = false; - for (i = 0; i < arr.length; i++) { - c = arr[i].toLowerCase(); - if (c === "e") c = "w"; // map ICU -> ilib - if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { - // ignore time components and the era - if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { - bad = true; - break; - } - } else { - dateComps.add(c); - } - } - if (!bad) { - comps = dateComps.asArray().sort(function (left, right) { - return (left < right) ? -1 : ((right < left) ? 1 : 0); - }); - this.dateComponents = comps.join(""); - } - } - - if (options.time) { - arr = options.time.split(""); - var timeComps = new ISet(); - this.badTime = false; - for (i = 0; i < arr.length; i++) { - c = arr[i].toLowerCase(); - if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { - // ignore the date components - if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { - this.badTime = true; - break; - } - } else { - timeComps.add(c); - } - } - if (!this.badTime) { - comps = timeComps.asArray().sort(function (left, right) { - return (left < right) ? -1 : ((right < left) ? 1 : 0); - }); - this.timeComponents = comps.join(""); - } - } - - if (options.clock && (options.clock === '12' || options.clock === '24')) { - this.clock = options.clock; - } - - if (options.template) { - // many options are not useful when specifying the template directly, so zero - // them out. - this.type = ""; - this.length = ""; - this.dateComponents = ""; - this.timeComponents = ""; - - this.template = options.template; - } - - if (options.timezone) { - if (options.timezone instanceof TimeZone) { - this.tz = options.timezone; - this.timezone = this.tz.getId(); - } else { - this.timezone = options.timezone; - } - } - - if (typeof(options.useNative) === 'boolean') { - this.useNative = options.useNative; - } - - if (typeof(options.meridiems) !== 'undefined' && - (options.meridiems === "chinese" || - options.meridiems === "gregorian" || - options.meridiems === "ethiopic")) { - this.meridiems = options.meridiems; - } - - if (typeof(options.sync) !== 'undefined') { - sync = (options.sync === true); - } - - loadParams = options.loadParams; - - new LocaleInfo(this.locale, { - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (li) { - this.locinfo = li; - - // get the default calendar name from the locale, and if the locale doesn't define - // one, use the hard-coded gregorian as the last resort - this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; - if (ilib.isDynCode()) { - // If we are running in the dynamic code loading assembly of ilib, the following - // will attempt to dynamically load the calendar date class for this calendar. If - // it doesn't work, this just goes on and it will use Gregorian instead. - DateFactory._dynLoadDate(this.calName); - } - - CalendarFactory({ - type: this.calName, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function(cal) { - this.cal = cal; - - if (!this.cal) { - // can be synchronous - this.cal = new GregorianCal(); - } - if (this.meridiems === "default") { - this.meridiems = li.getMeridiemsStyle(); - } - - // load the strings used to translate the components - new ResBundle({ - locale: this.locale, - name: "sysres", - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (rb) { - this.sysres = rb; - - if (!this.tz) { - var timezone = options.timezone; - if (!timezone && !options.locale) { - timezone = "local"; - } - - new TimeZone({ - locale: this.locale, - id: timezone, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function(tz) { - this.tz = tz; - this._init(options); - }) - }); - } else { - this._init(options); - } - }) - }); - }) - }); - }) - }); + var arr, i, bad, c, comps, + sync = true, + loadParams = undefined; + + this.locale = new Locale(); + this.type = "date"; + this.length = "s"; + this.dateComponents = "dmy"; + this.timeComponents = "ahm"; + this.meridiems = "default"; + + options = options || {sync: true}; + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.type) { + if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { + this.type = options.type; + } + } + + if (options.calendar) { + this.calName = options.calendar; + } + + if (options.length) { + if (options.length === 'short' || + options.length === 'medium' || + options.length === 'long' || + options.length === 'full') { + // only use the first char to save space in the json files + this.length = options.length.charAt(0); + } + } + + if (options.date) { + arr = options.date.split(""); + var dateComps = new ISet(); + bad = false; + for (i = 0; i < arr.length; i++) { + c = arr[i].toLowerCase(); + if (c === "e") c = "w"; // map ICU -> ilib + if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { + // ignore time components and the era + if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { + bad = true; + break; + } + } else { + dateComps.add(c); + } + } + if (!bad) { + comps = dateComps.asArray().sort(function (left, right) { + return (left < right) ? -1 : ((right < left) ? 1 : 0); + }); + this.dateComponents = comps.join(""); + } + } + + if (options.time) { + arr = options.time.split(""); + var timeComps = new ISet(); + this.badTime = false; + for (i = 0; i < arr.length; i++) { + c = arr[i].toLowerCase(); + if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { + // ignore the date components + if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { + this.badTime = true; + break; + } + } else { + timeComps.add(c); + } + } + if (!this.badTime) { + comps = timeComps.asArray().sort(function (left, right) { + return (left < right) ? -1 : ((right < left) ? 1 : 0); + }); + this.timeComponents = comps.join(""); + } + } + + if (options.clock && (options.clock === '12' || options.clock === '24')) { + this.clock = options.clock; + } + + if (options.template) { + // many options are not useful when specifying the template directly, so zero + // them out. + this.type = ""; + this.length = ""; + this.dateComponents = ""; + this.timeComponents = ""; + + this.template = options.template; + } + + if (options.timezone) { + if (options.timezone instanceof TimeZone) { + this.tz = options.timezone; + this.timezone = this.tz.getId(); + } else { + this.timezone = options.timezone; + } + } + + if (typeof(options.useNative) === 'boolean') { + this.useNative = options.useNative; + } + + if (typeof(options.meridiems) !== 'undefined' && + (options.meridiems === "chinese" || + options.meridiems === "gregorian" || + options.meridiems === "ethiopic")) { + this.meridiems = options.meridiems; + } + + if (typeof(options.sync) !== 'undefined') { + sync = (options.sync === true); + } + + loadParams = options.loadParams; + + new LocaleInfo(this.locale, { + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (li) { + this.locinfo = li; + + // get the default calendar name from the locale, and if the locale doesn't define + // one, use the hard-coded gregorian as the last resort + this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; + if (ilib.isDynCode()) { + // If we are running in the dynamic code loading assembly of ilib, the following + // will attempt to dynamically load the calendar date class for this calendar. If + // it doesn't work, this just goes on and it will use Gregorian instead. + DateFactory._init(this.calName); + } + + CalendarFactory({ + type: this.calName, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function(cal) { + this.cal = cal; + + if (!this.cal) { + // can be synchronous + this.cal = new GregorianCal(); + } + if (this.meridiems === "default") { + this.meridiems = li.getMeridiemsStyle(); + } + + // load the strings used to translate the components + new ResBundle({ + locale: this.locale, + name: "sysres", + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (rb) { + this.sysres = rb; + + if (!this.tz) { + var timezone = options.timezone; + if (!timezone && !options.locale) { + timezone = "local"; + } + + new TimeZone({ + locale: this.locale, + id: timezone, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function(tz) { + this.tz = tz; + this._init(options); + }) + }); + } else { + this._init(options); + } + }) + }); + }) + }); + }) + }); }; // used in getLength DateFmt.lenmap = { - "s": "short", - "m": "medium", - "l": "long", - "f": "full" + "s": "short", + "m": "medium", + "l": "long", + "f": "full" }; DateFmt.defaultFmt = { - "gregorian": { - "order": "{date} {time}", - "date": { - "dmwy": "EEE d/MM/yyyy", - "dmy": "d/MM/yyyy", - "dmw": "EEE d/MM", - "dm": "d/MM", - "my": "MM/yyyy", - "dw": "EEE d", - "d": "dd", - "m": "MM", - "y": "yyyy", - "n": "NN", - "w": "EEE" - }, - "time": { - "12": "h:mm:ssa", - "24": "H:mm:ss" - }, - "range": { - "c00": "{st} - {et}, {sd}/{sm}/{sy}", - "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", - "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", - "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", - "c10": "{sd}-{ed}/{sm}/{sy}", - "c11": "{sd}/{sm} - {ed}/{em} {sy}", - "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", - "c20": "{sm}/{sy} - {em}/{ey}", - "c30": "{sy} - {ey}" - } - }, - "islamic": "gregorian", - "hebrew": "gregorian", - "julian": "gregorian", - "buddhist": "gregorian", - "persian": "gregorian", - "persian-algo": "gregorian", - "han": "gregorian" + "gregorian": { + "order": "{date} {time}", + "date": { + "dmwy": "EEE d/MM/yyyy", + "dmy": "d/MM/yyyy", + "dmw": "EEE d/MM", + "dm": "d/MM", + "my": "MM/yyyy", + "dw": "EEE d", + "d": "dd", + "m": "MM", + "y": "yyyy", + "n": "NN", + "w": "EEE" + }, + "time": { + "12": "h:mm:ssa", + "24": "H:mm:ss" + }, + "range": { + "c00": "{st} - {et}, {sd}/{sm}/{sy}", + "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", + "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", + "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", + "c10": "{sd}-{ed}/{sm}/{sy}", + "c11": "{sd}/{sm} - {ed}/{em} {sy}", + "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", + "c20": "{sm}/{sy} - {em}/{ey}", + "c30": "{sy} - {ey}" + } + }, + "islamic": "gregorian", + "hebrew": "gregorian", + "julian": "gregorian", + "buddhist": "gregorian", + "persian": "gregorian", + "persian-algo": "gregorian", + "han": "gregorian" }; /** @@ -540,10 +540,10 @@ DateFmt.defaultFmt = { * @private */ DateFmt.monthNameLenMap = { - "short" : "N", - "medium": "NN", - "long": "MMM", - "full": "MMMM" + "short" : "N", + "medium": "NN", + "long": "MMM", + "full": "MMMM" }; /** @@ -551,14 +551,14 @@ DateFmt.monthNameLenMap = { * @private */ DateFmt.weekDayLenMap = { - "short" : "E", - "medium": "EE", - "long": "EEE", - "full": "EEEE" + "short" : "E", + "medium": "EE", + "long": "EEE", + "full": "EEEE" }; /** - * Return the range of possible meridiems (times of day like "AM" or + * Return the range of possible meridiems (times of day like "AM" or * "PM") in this date formatter.

* * The options may contain any of the following properties: @@ -566,13 +566,13 @@ DateFmt.weekDayLenMap = { *

    *
  • locale - locale to use when formatting the date/time. If the locale is * not specified, then the default locale of the app or web page will be used. - * - *
  • meridiems - string that specifies what style of meridiems to use with this - * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" - * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. + * + *
  • meridiems - string that specifies what style of meridiems to use with this + * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" + * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. * (For almost all locales, the Gregorian AM/PM style is most frequently used.) - * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", - * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding + * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", + * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" * when formatting dates in the Gregorian calendar. *
@@ -583,16 +583,16 @@ DateFmt.weekDayLenMap = { * @return {Array.<{name:string,start:string,end:string}>} */ DateFmt.getMeridiemsRange = function (options) { - options = options || {sync: true}; - var args = JSUtils.merge({}, options); - args.onLoad = function(fmt) { - if (typeof(options.onLoad) === "function") { - options.onLoad(fmt.getMeridiemsRange()); - } - }; - var fmt = new DateFmt(args); - - return fmt.getMeridiemsRange(); + options = options || {sync: true}; + var args = JSUtils.merge({}, options); + args.onLoad = function(fmt) { + if (typeof(options.onLoad) === "function") { + options.onLoad(fmt.getMeridiemsRange()); + } + }; + var fmt = new DateFmt(args); + + return fmt.getMeridiemsRange(); }; DateFmt.prototype = { @@ -606,22 +606,22 @@ DateFmt.prototype = { } if (!this.template) { Utils.loadData({ - object: "DateFmt", - locale: this.locale, - name: "dateformats.json", - sync: options.sync, - loadParams: options.loadParams, + object: "DateFmt", + locale: this.locale, + name: "dateformats.json", + sync: options.sync, + loadParams: options.loadParams, callback: ilib.bind(this, function (formats) { var spec = this.locale.getSpec().replace(/-/g, '_'); if (!formats) { formats = ilib.data.dateformats || DateFmt.defaultFmt; } - + if (typeof(this.clock) === 'undefined') { // default to the locale instead this.clock = this.locinfo.getClock(); } - + var ret = this; if (typeof(options.sync) === "boolean" && !options.sync) { @@ -645,965 +645,965 @@ DateFmt.prototype = { }); } else { this._massageTemplate(); - + if (typeof(options.onLoad) === 'function') { options.onLoad(this); } } }, - - /** - * @protected - * @param {string|{ - * order:(string|{ - * s:string, - * m:string, - * l:string, - * f:string - * }), - * date:Object., - * time:Object.>, - * range:Object. - * }} formats - */ - _initTemplate: function (formats) { - if (formats[this.calName]) { - var name = formats[this.calName]; - // may be an alias to another calendar type - this.formats = (typeof(name) === "string") ? formats[name] : name; - - this.template = ""; - - switch (this.type) { - case "datetime": - this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; - this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); - this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); - break; - case "date": - this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); - break; - case "time": - this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); - break; - } - - // calculate what order the components appear in for this locale - this.componentOrder = this._getFormat(this.formats.date, "dmy", "l"). - replace(/[^dMy]/g, ""). - replace(/y+/, "y"). - replace(/d+/, "d"). - replace(/M+/, "m"); - } else { - throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); - } - }, - - /** - * @protected - */ - _massageTemplate: function () { - var i; - - if (this.clock && this.template) { - // explicitly set the hours to the requested type - var temp = ""; - switch (this.clock) { - case "24": - for (i = 0; i < this.template.length; i++) { - if (this.template.charAt(i) == "'") { - temp += this.template.charAt(i++); - while (i < this.template.length && this.template.charAt(i) !== "'") { - temp += this.template.charAt(i++); - } - if (i < this.template.length) { - temp += this.template.charAt(i); - } - } else if (this.template.charAt(i) == 'K') { - temp += 'k'; - } else if (this.template.charAt(i) == 'h') { - temp += 'H'; - } else { - temp += this.template.charAt(i); - } - } - this.template = temp; - break; - case "12": - for (i = 0; i < this.template.length; i++) { - if (this.template.charAt(i) == "'") { - temp += this.template.charAt(i++); - while (i < this.template.length && this.template.charAt(i) !== "'") { - temp += this.template.charAt(i++); - } - if (i < this.template.length) { - temp += this.template.charAt(i); - } - } else if (this.template.charAt(i) == 'k') { - temp += 'K'; - } else if (this.template.charAt(i) == 'H') { - temp += 'h'; - } else { - temp += this.template.charAt(i); - } - } - this.template = temp; - break; - } - } - - // tokenize it now for easy formatting - this.templateArr = this._tokenize(this.template); - - var digits; - // set up the mapping to native or alternate digits if necessary - if (typeof(this.useNative) === "boolean") { - if (this.useNative) { - digits = this.locinfo.getNativeDigits(); - if (digits) { - this.digits = digits; - } - } - } else if (this.locinfo.getDigitsStyle() === "native") { - digits = this.locinfo.getNativeDigits(); - if (digits) { - this.useNative = true; - this.digits = digits; - } - } - }, - - /** - * Convert the template into an array of date components separated by formatting chars. - * @protected - * @param {string} template Format template to tokenize into components - * @return {Array.} a tokenized array of date format components - */ - _tokenize: function (template) { - var i = 0, start, ch, letter, arr = []; - - // console.log("_tokenize: tokenizing template " + template); - if (template) { - while (i < template.length) { - ch = template.charAt(i); - start = i; - if (ch === "'") { - // console.log("found quoted string"); - i++; - // escaped string - push as-is, then dequote later - while (i < template.length && template.charAt(i) !== "'") { - i++; - } - if (i < template.length) { - i++; // grab the other quote too - } - } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { - letter = template.charAt(i); - // console.log("found letters " + letter); - while (i < template.length && ch === letter) { - ch = template.charAt(++i); - } - } else { - // console.log("found other"); - while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { - ch = template.charAt(++i); - } - } - arr.push(template.substring(start,i)); - // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); - } - } - return arr; - }, - - /** - * @protected - * @param {Object.} obj Object to search - * @param {string} components Format components to search - * @param {string} length Length of the requested format - * @return {string|undefined} the requested format - */ - _getFormatInternal: function getFormatInternal(obj, components, length) { - if (typeof(components) !== 'undefined' && obj && obj[components]) { - return this._getLengthFormat(obj[components], length); - } - return undefined; - }, - - // stand-alone of m (month) is l - // stand-alone of d (day) is a - // stand-alone of w (weekday) is e - // stand-alone of y (year) is r - _standAlones: { - "m": "l", - "d": "a", - "w": "e", - "y": "r" - }, - - /** - * @protected - * @param {Object.} obj Object to search - * @param {string} components Format components to search - * @param {string} length Length of the requested format - * @return {string|undefined} the requested format - */ - _getFormat: function getFormat(obj, components, length) { - // handle some special cases for stand-alone formats - if (components && this._standAlones[components]) { - var tmp = this._getFormatInternal(obj, this._standAlones[components], length); - if (tmp) { - return tmp; - } - } - - // if no stand-alone format is available, fall back to the regular format - return this._getFormatInternal(obj, components, length); - }, - - /** - * @protected - * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search - * @param {string} length Length of the requested format - * @return {(string|undefined)} the requested format - */ - _getLengthFormat: function getLengthFormat(obj, length) { - if (typeof(obj) === 'string') { - return obj; - } else if (obj[length]) { - return obj[length]; - } - return undefined; - }, - - /** - * Return the locale used with this formatter instance. - * @return {Locale} the Locale instance for this formatter - */ - getLocale: function() { - return this.locale; - }, - - /** - * Return the template string that is used to format date/times for this - * formatter instance. This will work, even when the template property is not explicitly - * given in the options to the constructor. Without the template option, the constructor - * will build the appropriate template according to the options and use that template - * in the format method. - * - * @return {string} the format template for this formatter - */ - getTemplate: function() { - return this.template; - }, - - /** - * Return the order of the year, month, and date components for the current locale.

- * - * When implementing a date input widget in a UI, it would be useful to know what - * order to put the year, month, and date input fields so that it conforms to the - * user expectations for the locale. This method gives that order by returning a - * string that has a single "y", "m", and "d" character in it in the correct - * order.

- * - * For example, the return value "ymd" means that this locale formats the year first, - * the month second, and the date third, and "mdy" means that the month is first, - * the date is second, and the year is third. Four of the 6 possible permutations - * of the three letters have at least one locale that uses that ordering, though some - * combinations are far more likely than others. The ones that are not used by any - * locales are "dym" and "myd", though new locales are still being added to - * CLDR frequently, and possible orderings cannot be predicted. Your code should - * support all 6 possibilities, just in case. - * - * @return {string} a string giving the date component order - */ - getDateComponentOrder: function() { - return this.componentOrder; - }, - - /** - * Return the type of this formatter. The type is a string that has one of the following - * values: "time", "date", "datetime". - * @return {string} the type of the formatter - */ - getType: function() { - return this.type; - }, - - /** - * Return the name of the calendar used to format date/times for this - * formatter instance. - * @return {string} the name of the calendar used by this formatter - */ - getCalendar: function () { - return this.cal.getType(); - }, - - /** - * Return the length used to format date/times in this formatter. This is either the - * value of the length option to the constructor, or the default value. - * - * @return {string} the length of formats this formatter returns - */ - getLength: function () { - return DateFmt.lenmap[this.length] || ""; - }, - - /** - * Return the date components that this formatter formats. This is either the - * value of the date option to the constructor, or the default value. If this - * formatter is a time-only formatter, this method will return the empty - * string. The date component letters may be specified in any order in the - * constructor, but this method will reorder the given components to a standard - * order. - * - * @return {string} the date components that this formatter formats - */ - getDateComponents: function () { - return this.dateComponents || ""; - }, - - /** - * Return the time components that this formatter formats. This is either the - * value of the time option to the constructor, or the default value. If this - * formatter is a date-only formatter, this method will return the empty - * string. The time component letters may be specified in any order in the - * constructor, but this method will reorder the given components to a standard - * order. - * - * @return {string} the time components that this formatter formats - */ - getTimeComponents: function () { - return this.timeComponents || ""; - }, - - /** - * Return the time zone used to format date/times for this formatter - * instance. - * @return {TimeZone} a time zone object that this formatter is formatting for - */ - getTimeZone: function () { - return this.tz; - }, - - /** - * Return the clock option set in the constructor. If the clock option was - * not given, the default from the locale is returned instead. - * @return {string} "12" or "24" depending on whether this formatter uses - * the 12-hour or 24-hour clock - */ - getClock: function () { - return this.clock || this.locinfo.getClock(); - }, - - /** - * Return the meridiems range in current locale. - * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems - */ - getMeridiemsRange: function () { - var result; - var _getSysString = function (key) { - return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); - }; - - switch (this.meridiems) { - case "chinese": - result = [ - { - name: _getSysString.call(this, "azh0"), - start: "00:00", - end: "05:59" - }, - { - name: _getSysString.call(this, "azh1"), - start: "06:00", - end: "08:59" - }, - { - name: _getSysString.call(this, "azh2"), - start: "09:00", - end: "11:59" - }, - { - name: _getSysString.call(this, "azh3"), - start: "12:00", - end: "12:59" - }, - { - name: _getSysString.call(this, "azh4"), - start: "13:00", - end: "17:59" - }, - { - name: _getSysString.call(this, "azh5"), - start: "18:00", - end: "20:59" - }, - { - name: _getSysString.call(this, "azh6"), - start: "21:00", - end: "23:59" - } - ]; - break; - case "ethiopic": - result = [ - { - name: _getSysString.call(this, "a0-ethiopic"), - start: "00:00", - end: "05:59" - }, - { - name: _getSysString.call(this, "a1-ethiopic"), - start: "06:00", - end: "06:00" - }, - { - name: _getSysString.call(this, "a2-ethiopic"), - start: "06:01", - end: "11:59" - }, - { - name: _getSysString.call(this, "a3-ethiopic"), - start: "12:00", - end: "17:59" - }, - { - name: _getSysString.call(this, "a4-ethiopic"), - start: "18:00", - end: "23:59" - } - ]; - break; - default: - result = [ - { - name: _getSysString.call(this, "a0"), - start: "00:00", - end: "11:59" - }, - { - name: _getSysString.call(this, "a1"), - start: "12:00", - end: "23:59" - } - ]; - break; - } - - return result; - }, - - /** - * @private - */ - _getTemplate: function (prefix, calendar) { - if (calendar !== "gregorian") { - return prefix + "-" + calendar; - } - return prefix; - }, - - /** - * Returns an array of the months of the year, formatted to the optional length specified. - * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) - *

- * The options parameter may contain any of the following properties: - * - *

    - *
  • length - length of the names of the months being sought. This may be one of - * "short", "medium", "long", or "full" - *
  • date - retrieve the names of the months in the date of the given date - *
  • year - retrieve the names of the months in the given year. In some calendars, - * the months have different names depending if that year is a leap year or not. - *
- * - * @param {Object=} options an object-literal that contains any of the above properties - * @return {Array} an array of the names of all of the months of the year in the current calendar - */ - getMonthsOfYear: function(options) { - var length = (options && options.length) || this.getLength(), - template = DateFmt.monthNameLenMap[length], - months = [undefined], - date, - monthCount; - - if (options) { - if (options.date) { - date = DateFactory._dateToIlib(options.date); - } - - if (options.year) { - date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); - } - } - - if (!date) { - date = DateFactory({ - calendar: this.cal.getType() - }); - } - - monthCount = this.cal.getNumMonths(date.getYears()); - for (var i = 1; i <= monthCount; i++) { - months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); - } - return months; - }, - - /** - * Returns an array of the days of the week, formatted to the optional length specified. - * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) - *

- * The options parameter may contain any of the following properties: - * - *

    - *
  • length - length of the names of the months being sought. This may be one of - * "short", "medium", "long", or "full" - *
- * @param {Object=} options an object-literal that contains one key - * "length" with the standard length strings - * @return {Array} an array of all of the names of the days of the week - */ - getDaysOfWeek: function(options) { - var length = (options && options.length) || this.getLength(), - template = DateFmt.weekDayLenMap[length], - days = []; - for (var i = 0; i < 7; i++) { - days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); - } - return days; - }, - - - /** - * Convert this formatter to a string representation by returning the - * format template. This method delegates to getTemplate. - * - * @return {string} the format template - */ - toString: function() { - return this.getTemplate(); - }, - - /** - * @private - * Format a date according to a sequence of components. - * @param {IDate} date a date/time object to format - * @param {Array.} templateArr an array of components to format - * @return {string} the formatted date - */ - _formatTemplate: function (date, templateArr) { - var i, key, temp, tz, str = ""; - for (i = 0; i < templateArr.length; i++) { - switch (templateArr[i]) { - case 'd': - str += (date.day || 1); - break; - case 'dd': - str += JSUtils.pad(date.day || "1", 2); - break; - case 'yy': - temp = "" + ((date.year || 0) % 100); - str += JSUtils.pad(temp, 2); - break; - case 'yyyy': - str += JSUtils.pad(date.year || "0", 4); - break; - case 'M': - str += (date.month || 1); - break; - case 'MM': - str += JSUtils.pad(date.month || "1", 2); - break; - case 'h': - temp = (date.hour || 0) % 12; - if (temp == 0) { - temp = "12"; - } - str += temp; - break; - case 'hh': - temp = (date.hour || 0) % 12; - if (temp == 0) { - temp = "12"; - } - str += JSUtils.pad(temp, 2); - break; - /* - case 'j': - temp = (date.hour || 0) % 12 + 1; - str += temp; - break; - case 'jj': - temp = (date.hour || 0) % 12 + 1; - str += JSUtils.pad(temp, 2); - break; - */ - case 'K': - temp = (date.hour || 0) % 12; - str += temp; - break; - case 'KK': - temp = (date.hour || 0) % 12; - str += JSUtils.pad(temp, 2); - break; - - case 'H': - str += (date.hour || "0"); - break; - case 'HH': - str += JSUtils.pad(date.hour || "0", 2); - break; - case 'k': - str += (date.hour == 0 ? "24" : date.hour); - break; - case 'kk': - temp = (date.hour == 0 ? "24" : date.hour); - str += JSUtils.pad(temp, 2); - break; - - case 'm': - str += (date.minute || "0"); - break; - case 'mm': - str += JSUtils.pad(date.minute || "0", 2); - break; - case 's': - str += (date.second || "0"); - break; - case 'ss': - str += JSUtils.pad(date.second || "0", 2); - break; - case 'S': - str += (date.millisecond || "0"); - break; - case 'SSS': - str += JSUtils.pad(date.millisecond || "0", 3); - break; - - case 'N': - case 'NN': - case 'MMM': - case 'MMMM': - case 'L': - case 'LL': - case 'LLL': - case 'LLLL': - key = templateArr[i] + (date.month || 1); - str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); - break; - - case 'E': - case 'EE': - case 'EEE': - case 'EEEE': - case 'c': - case 'cc': - case 'ccc': - case 'cccc': - key = templateArr[i] + date.getDayOfWeek(); - //console.log("finding " + key + " in the resources"); - str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); - break; - - case 'a': - switch (this.meridiems) { - case "chinese": - if (date.hour < 6) { - key = "azh0"; // before dawn - } else if (date.hour < 9) { - key = "azh1"; // morning - } else if (date.hour < 12) { - key = "azh2"; // late morning/day before noon - } else if (date.hour < 13) { - key = "azh3"; // noon hour/midday - } else if (date.hour < 18) { - key = "azh4"; // afternoon - } else if (date.hour < 21) { - key = "azh5"; // evening time/dusk - } else { - key = "azh6"; // night time - } - break; - case "ethiopic": - if (date.hour < 6) { - key = "a0-ethiopic"; // morning - } else if (date.hour === 6 && date.minute === 0) { - key = "a1-ethiopic"; // noon - } else if (date.hour >= 6 && date.hour < 12) { - key = "a2-ethiopic"; // afternoon - } else if (date.hour >= 12 && date.hour < 18) { - key = "a3-ethiopic"; // evening - } else if (date.hour >= 18) { - key = "a4-ethiopic"; // night - } - break; - default: - key = date.hour < 12 ? "a0" : "a1"; - break; - } - //console.log("finding " + key + " in the resources"); - str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); - break; - - case 'w': - str += date.getWeekOfYear(); - break; - case 'ww': - str += JSUtils.pad(date.getWeekOfYear(), 2); - break; - - case 'D': - str += date.getDayOfYear(); - break; - case 'DD': - str += JSUtils.pad(date.getDayOfYear(), 2); - break; - case 'DDD': - str += JSUtils.pad(date.getDayOfYear(), 3); - break; - case 'W': - str += date.getWeekOfMonth(this.locale); - break; - - case 'G': - key = "G" + date.getEra(); - str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); - break; - - case 'O': - temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); - str += temp.formatChoice(date.day, {num: date.day}); - break; - - case 'z': // general time zone - tz = this.getTimeZone(); // lazy-load the tz - str += tz.getDisplayName(date, "standard"); - break; - case 'Z': // RFC 822 time zone - tz = this.getTimeZone(); // lazy-load the tz - str += tz.getDisplayName(date, "rfc822"); - break; - - default: - str += templateArr[i].replace(/'/g, ""); - break; - } - } - - if (this.digits) { - str = JSUtils.mapString(str, this.digits); - } - return str; - }, - - /** - * Format a particular date instance according to the settings of this - * formatter object. The type of the date instance being formatted must - * correspond exactly to the calendar type with which this formatter was - * constructed. If the types are not compatible, this formatter will - * produce bogus results. - * - * @param {IDate|number|string|Date|JulianDay|null|undefined} dateLike a date-like object to format - * @return {string} the formatted version of the given date instance - */ - format: function (dateLike) { - var thisZoneName = this.tz && this.tz.getId() || "local"; - - var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); - - if (!date.getCalendar || !(date instanceof IDate)) { - throw "Wrong date type passed to DateFmt.format()"; - } - - var dateZoneName = date.timezone || "local"; - - // convert to the time zone of this formatter before formatting - if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { - // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); - // this will recalculate the date components based on the new time zone - // and/or convert a date in another calendar to the current calendar before formatting it - var newDate = DateFactory({ - type: this.calName, - timezone: thisZoneName, - julianday: date.getJulianDay() - }); - - date = newDate; - } - return this._formatTemplate(date, this.templateArr); - }, - - /** - * Return a string that describes a date relative to the given - * reference date. The string returned is text that for the locale that - * was specified when the formatter instance was constructed.

- * - * The date can be in the future relative to the reference date or in - * the past, and the formatter will generate the appropriate string.

- * - * The text used to describe the relative reference depends on the length - * of time between the date and the reference. If the time was in the - * past, it will use the "ago" phrase, and in the future, it will use - * the "in" phrase. Examples:

- * - *

    - *
  • within a minute: either "X seconds ago" or "in X seconds" - *
  • within an hour: either "X minutes ago" or "in X minutes" - *
  • within a day: either "X hours ago" or "in X hours" - *
  • within 2 weeks: either "X days ago" or "in X days" - *
  • within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" - *
  • within two years: either "X months ago" or "in X months" - *
  • longer than 2 years: "X years ago" or "in X years" - *
- * - * @param {IDate|number|string|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to - * @param {IDate|number|string|Date|JulianDay|null|undefined} date a date being formatted - * @throws "Wrong calendar type" when the start or end dates are not the same - * calendar type as the formatter itself - * @return {string} the formatted relative date - */ - formatRelative: function(reference, date) { - reference = DateFactory._dateToIlib(reference); - date = DateFactory._dateToIlib(date); - - var referenceRd, dateRd, fmt, diff, absDiff, num; - - if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || - typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { - throw "Wrong calendar type"; - } - - referenceRd = reference.getRataDie(); - dateRd = date.getRataDie(); - - diff = referenceRd - dateRd; - absDiff = Math.abs(diff); - - if (absDiff < 0.000694444) { - num = Math.round(absDiff * 86400); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}s ago") : this.sysres.getString("#in {num}s"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 sec ago|#{num} sec ago") : this.sysres.getString("1#in 1 sec|#in {num} sec"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 second ago|#{num} seconds ago") : this.sysres.getString("1#in 1 second|#in {num} seconds"); - break; - } - } else if (absDiff < 0.041666667) { - num = Math.round(absDiff * 1440); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}mi ago") : this.sysres.getString("#in {num}mi"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 min ago|#{num} min ago") : this.sysres.getString("1#in 1 min|#in {num} min"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 minute ago|#{num} minutes ago") : this.sysres.getString("1#in 1 minute|#in {num} minutes"); - break; - } - } else if (absDiff < 1) { - num = Math.round(absDiff * 24); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}h ago") : this.sysres.getString("#in {num}h"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 hr ago|#{num} hrs ago") : this.sysres.getString("1#in 1 hr|#in {num} hrs"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 hour ago|#{num} hours ago") : this.sysres.getString("1#in 1 hour|#in {num} hours"); - break; - } - } else if (absDiff < 14) { - num = Math.round(absDiff); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}d ago") : this.sysres.getString("#in {num}d"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 dy ago|#{nudurationm} dys ago") : this.sysres.getString("1#in 1 dy|#in {num} dys"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 day ago|#{num} days ago") : this.sysres.getString("1#in 1 day|#in {num} days"); - break; - } - } else if (absDiff < 84) { - num = Math.round(absDiff/7); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}w ago") : this.sysres.getString("#in {num}w"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 wk ago|#{num} wks ago") : this.sysres.getString("1#in 1 wk|#in {num} wks"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 week ago|#{num} weeks ago") : this.sysres.getString("1#in 1 week|#in {num} weeks"); - break; - } - } else if (absDiff < 730) { - num = Math.round(absDiff/30.4); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}mo ago") : this.sysres.getString("#in {num}mo"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 mon ago|#{num} mons ago") : this.sysres.getString("1#in 1 mon|#in {num} mons"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 month ago|#{num} months ago") : this.sysres.getString("1#in 1 month|#in {num} months"); - break; - } - } else { - num = Math.round(absDiff/365); - switch (this.length) { - case 's': - fmt = diff > 0 ? this.sysres.getString("#{num}y ago") : this.sysres.getString("#in {num}y"); - break; - case 'm': - fmt = diff > 0 ? this.sysres.getString("1#1 yr ago|#{num} yrs ago") : this.sysres.getString("1#in 1 yr|#in {num} yrs"); - break; - default: - case 'f': - case 'l': - fmt = diff > 0 ? this.sysres.getString("1#1 year ago|#{num} years ago") : this.sysres.getString("1#in 1 year|#in {num} years"); - break; - } - } - return fmt.formatChoice(num, {num: num}); - } + + /** + * @protected + * @param {string|{ + * order:(string|{ + * s:string, + * m:string, + * l:string, + * f:string + * }), + * date:Object., + * time:Object.>, + * range:Object. + * }} formats + */ + _initTemplate: function (formats) { + if (formats[this.calName]) { + var name = formats[this.calName]; + // may be an alias to another calendar type + this.formats = (typeof(name) === "string") ? formats[name] : name; + + this.template = ""; + + switch (this.type) { + case "datetime": + this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; + this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); + this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); + break; + case "date": + this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); + break; + case "time": + this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); + break; + } + + // calculate what order the components appear in for this locale + this.componentOrder = this._getFormat(this.formats.date, "dmy", "l"). + replace(/[^dMy]/g, ""). + replace(/y+/, "y"). + replace(/d+/, "d"). + replace(/M+/, "m"); + } else { + throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); + } + }, + + /** + * @protected + */ + _massageTemplate: function () { + var i; + + if (this.clock && this.template) { + // explicitly set the hours to the requested type + var temp = ""; + switch (this.clock) { + case "24": + for (i = 0; i < this.template.length; i++) { + if (this.template.charAt(i) == "'") { + temp += this.template.charAt(i++); + while (i < this.template.length && this.template.charAt(i) !== "'") { + temp += this.template.charAt(i++); + } + if (i < this.template.length) { + temp += this.template.charAt(i); + } + } else if (this.template.charAt(i) == 'K') { + temp += 'k'; + } else if (this.template.charAt(i) == 'h') { + temp += 'H'; + } else { + temp += this.template.charAt(i); + } + } + this.template = temp; + break; + case "12": + for (i = 0; i < this.template.length; i++) { + if (this.template.charAt(i) == "'") { + temp += this.template.charAt(i++); + while (i < this.template.length && this.template.charAt(i) !== "'") { + temp += this.template.charAt(i++); + } + if (i < this.template.length) { + temp += this.template.charAt(i); + } + } else if (this.template.charAt(i) == 'k') { + temp += 'K'; + } else if (this.template.charAt(i) == 'H') { + temp += 'h'; + } else { + temp += this.template.charAt(i); + } + } + this.template = temp; + break; + } + } + + // tokenize it now for easy formatting + this.templateArr = this._tokenize(this.template); + + var digits; + // set up the mapping to native or alternate digits if necessary + if (typeof(this.useNative) === "boolean") { + if (this.useNative) { + digits = this.locinfo.getNativeDigits(); + if (digits) { + this.digits = digits; + } + } + } else if (this.locinfo.getDigitsStyle() === "native") { + digits = this.locinfo.getNativeDigits(); + if (digits) { + this.useNative = true; + this.digits = digits; + } + } + }, + + /** + * Convert the template into an array of date components separated by formatting chars. + * @protected + * @param {string} template Format template to tokenize into components + * @return {Array.} a tokenized array of date format components + */ + _tokenize: function (template) { + var i = 0, start, ch, letter, arr = []; + + // console.log("_tokenize: tokenizing template " + template); + if (template) { + while (i < template.length) { + ch = template.charAt(i); + start = i; + if (ch === "'") { + // console.log("found quoted string"); + i++; + // escaped string - push as-is, then dequote later + while (i < template.length && template.charAt(i) !== "'") { + i++; + } + if (i < template.length) { + i++; // grab the other quote too + } + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + letter = template.charAt(i); + // console.log("found letters " + letter); + while (i < template.length && ch === letter) { + ch = template.charAt(++i); + } + } else { + // console.log("found other"); + while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { + ch = template.charAt(++i); + } + } + arr.push(template.substring(start,i)); + // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); + } + } + return arr; + }, + + /** + * @protected + * @param {Object.} obj Object to search + * @param {string} components Format components to search + * @param {string} length Length of the requested format + * @return {string|undefined} the requested format + */ + _getFormatInternal: function getFormatInternal(obj, components, length) { + if (typeof(components) !== 'undefined' && obj && obj[components]) { + return this._getLengthFormat(obj[components], length); + } + return undefined; + }, + + // stand-alone of m (month) is l + // stand-alone of d (day) is a + // stand-alone of w (weekday) is e + // stand-alone of y (year) is r + _standAlones: { + "m": "l", + "d": "a", + "w": "e", + "y": "r" + }, + + /** + * @protected + * @param {Object.} obj Object to search + * @param {string} components Format components to search + * @param {string} length Length of the requested format + * @return {string|undefined} the requested format + */ + _getFormat: function getFormat(obj, components, length) { + // handle some special cases for stand-alone formats + if (components && this._standAlones[components]) { + var tmp = this._getFormatInternal(obj, this._standAlones[components], length); + if (tmp) { + return tmp; + } + } + + // if no stand-alone format is available, fall back to the regular format + return this._getFormatInternal(obj, components, length); + }, + + /** + * @protected + * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search + * @param {string} length Length of the requested format + * @return {(string|undefined)} the requested format + */ + _getLengthFormat: function getLengthFormat(obj, length) { + if (typeof(obj) === 'string') { + return obj; + } else if (obj[length]) { + return obj[length]; + } + return undefined; + }, + + /** + * Return the locale used with this formatter instance. + * @return {Locale} the Locale instance for this formatter + */ + getLocale: function() { + return this.locale; + }, + + /** + * Return the template string that is used to format date/times for this + * formatter instance. This will work, even when the template property is not explicitly + * given in the options to the constructor. Without the template option, the constructor + * will build the appropriate template according to the options and use that template + * in the format method. + * + * @return {string} the format template for this formatter + */ + getTemplate: function() { + return this.template; + }, + + /** + * Return the order of the year, month, and date components for the current locale.

+ * + * When implementing a date input widget in a UI, it would be useful to know what + * order to put the year, month, and date input fields so that it conforms to the + * user expectations for the locale. This method gives that order by returning a + * string that has a single "y", "m", and "d" character in it in the correct + * order.

+ * + * For example, the return value "ymd" means that this locale formats the year first, + * the month second, and the date third, and "mdy" means that the month is first, + * the date is second, and the year is third. Four of the 6 possible permutations + * of the three letters have at least one locale that uses that ordering, though some + * combinations are far more likely than others. The ones that are not used by any + * locales are "dym" and "myd", though new locales are still being added to + * CLDR frequently, and possible orderings cannot be predicted. Your code should + * support all 6 possibilities, just in case. + * + * @return {string} a string giving the date component order + */ + getDateComponentOrder: function() { + return this.componentOrder; + }, + + /** + * Return the type of this formatter. The type is a string that has one of the following + * values: "time", "date", "datetime". + * @return {string} the type of the formatter + */ + getType: function() { + return this.type; + }, + + /** + * Return the name of the calendar used to format date/times for this + * formatter instance. + * @return {string} the name of the calendar used by this formatter + */ + getCalendar: function () { + return this.cal.getType(); + }, + + /** + * Return the length used to format date/times in this formatter. This is either the + * value of the length option to the constructor, or the default value. + * + * @return {string} the length of formats this formatter returns + */ + getLength: function () { + return DateFmt.lenmap[this.length] || ""; + }, + + /** + * Return the date components that this formatter formats. This is either the + * value of the date option to the constructor, or the default value. If this + * formatter is a time-only formatter, this method will return the empty + * string. The date component letters may be specified in any order in the + * constructor, but this method will reorder the given components to a standard + * order. + * + * @return {string} the date components that this formatter formats + */ + getDateComponents: function () { + return this.dateComponents || ""; + }, + + /** + * Return the time components that this formatter formats. This is either the + * value of the time option to the constructor, or the default value. If this + * formatter is a date-only formatter, this method will return the empty + * string. The time component letters may be specified in any order in the + * constructor, but this method will reorder the given components to a standard + * order. + * + * @return {string} the time components that this formatter formats + */ + getTimeComponents: function () { + return this.timeComponents || ""; + }, + + /** + * Return the time zone used to format date/times for this formatter + * instance. + * @return {TimeZone} a time zone object that this formatter is formatting for + */ + getTimeZone: function () { + return this.tz; + }, + + /** + * Return the clock option set in the constructor. If the clock option was + * not given, the default from the locale is returned instead. + * @return {string} "12" or "24" depending on whether this formatter uses + * the 12-hour or 24-hour clock + */ + getClock: function () { + return this.clock || this.locinfo.getClock(); + }, + + /** + * Return the meridiems range in current locale. + * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems + */ + getMeridiemsRange: function () { + var result; + var _getSysString = function (key) { + return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); + }; + + switch (this.meridiems) { + case "chinese": + result = [ + { + name: _getSysString.call(this, "azh0"), + start: "00:00", + end: "05:59" + }, + { + name: _getSysString.call(this, "azh1"), + start: "06:00", + end: "08:59" + }, + { + name: _getSysString.call(this, "azh2"), + start: "09:00", + end: "11:59" + }, + { + name: _getSysString.call(this, "azh3"), + start: "12:00", + end: "12:59" + }, + { + name: _getSysString.call(this, "azh4"), + start: "13:00", + end: "17:59" + }, + { + name: _getSysString.call(this, "azh5"), + start: "18:00", + end: "20:59" + }, + { + name: _getSysString.call(this, "azh6"), + start: "21:00", + end: "23:59" + } + ]; + break; + case "ethiopic": + result = [ + { + name: _getSysString.call(this, "a0-ethiopic"), + start: "00:00", + end: "05:59" + }, + { + name: _getSysString.call(this, "a1-ethiopic"), + start: "06:00", + end: "06:00" + }, + { + name: _getSysString.call(this, "a2-ethiopic"), + start: "06:01", + end: "11:59" + }, + { + name: _getSysString.call(this, "a3-ethiopic"), + start: "12:00", + end: "17:59" + }, + { + name: _getSysString.call(this, "a4-ethiopic"), + start: "18:00", + end: "23:59" + } + ]; + break; + default: + result = [ + { + name: _getSysString.call(this, "a0"), + start: "00:00", + end: "11:59" + }, + { + name: _getSysString.call(this, "a1"), + start: "12:00", + end: "23:59" + } + ]; + break; + } + + return result; + }, + + /** + * @private + */ + _getTemplate: function (prefix, calendar) { + if (calendar !== "gregorian") { + return prefix + "-" + calendar; + } + return prefix; + }, + + /** + * Returns an array of the months of the year, formatted to the optional length specified. + * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) + *

+ * The options parameter may contain any of the following properties: + * + *

    + *
  • length - length of the names of the months being sought. This may be one of + * "short", "medium", "long", or "full" + *
  • date - retrieve the names of the months in the date of the given date + *
  • year - retrieve the names of the months in the given year. In some calendars, + * the months have different names depending if that year is a leap year or not. + *
+ * + * @param {Object=} options an object-literal that contains any of the above properties + * @return {Array} an array of the names of all of the months of the year in the current calendar + */ + getMonthsOfYear: function(options) { + var length = (options && options.length) || this.getLength(), + template = DateFmt.monthNameLenMap[length], + months = [undefined], + date, + monthCount; + + if (options) { + if (options.date) { + date = DateFactory._dateToIlib(options.date); + } + + if (options.year) { + date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); + } + } + + if (!date) { + date = DateFactory({ + calendar: this.cal.getType() + }); + } + + monthCount = this.cal.getNumMonths(date.getYears()); + for (var i = 1; i <= monthCount; i++) { + months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); + } + return months; + }, + + /** + * Returns an array of the days of the week, formatted to the optional length specified. + * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) + *

+ * The options parameter may contain any of the following properties: + * + *

    + *
  • length - length of the names of the months being sought. This may be one of + * "short", "medium", "long", or "full" + *
+ * @param {Object=} options an object-literal that contains one key + * "length" with the standard length strings + * @return {Array} an array of all of the names of the days of the week + */ + getDaysOfWeek: function(options) { + var length = (options && options.length) || this.getLength(), + template = DateFmt.weekDayLenMap[length], + days = []; + for (var i = 0; i < 7; i++) { + days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); + } + return days; + }, + + + /** + * Convert this formatter to a string representation by returning the + * format template. This method delegates to getTemplate. + * + * @return {string} the format template + */ + toString: function() { + return this.getTemplate(); + }, + + /** + * @private + * Format a date according to a sequence of components. + * @param {IDate} date a date/time object to format + * @param {Array.} templateArr an array of components to format + * @return {string} the formatted date + */ + _formatTemplate: function (date, templateArr) { + var i, key, temp, tz, str = ""; + for (i = 0; i < templateArr.length; i++) { + switch (templateArr[i]) { + case 'd': + str += (date.day || 1); + break; + case 'dd': + str += JSUtils.pad(date.day || "1", 2); + break; + case 'yy': + temp = "" + ((date.year || 0) % 100); + str += JSUtils.pad(temp, 2); + break; + case 'yyyy': + str += JSUtils.pad(date.year || "0", 4); + break; + case 'M': + str += (date.month || 1); + break; + case 'MM': + str += JSUtils.pad(date.month || "1", 2); + break; + case 'h': + temp = (date.hour || 0) % 12; + if (temp == 0) { + temp = "12"; + } + str += temp; + break; + case 'hh': + temp = (date.hour || 0) % 12; + if (temp == 0) { + temp = "12"; + } + str += JSUtils.pad(temp, 2); + break; + /* + case 'j': + temp = (date.hour || 0) % 12 + 1; + str += temp; + break; + case 'jj': + temp = (date.hour || 0) % 12 + 1; + str += JSUtils.pad(temp, 2); + break; + */ + case 'K': + temp = (date.hour || 0) % 12; + str += temp; + break; + case 'KK': + temp = (date.hour || 0) % 12; + str += JSUtils.pad(temp, 2); + break; + + case 'H': + str += (date.hour || "0"); + break; + case 'HH': + str += JSUtils.pad(date.hour || "0", 2); + break; + case 'k': + str += (date.hour == 0 ? "24" : date.hour); + break; + case 'kk': + temp = (date.hour == 0 ? "24" : date.hour); + str += JSUtils.pad(temp, 2); + break; + + case 'm': + str += (date.minute || "0"); + break; + case 'mm': + str += JSUtils.pad(date.minute || "0", 2); + break; + case 's': + str += (date.second || "0"); + break; + case 'ss': + str += JSUtils.pad(date.second || "0", 2); + break; + case 'S': + str += (date.millisecond || "0"); + break; + case 'SSS': + str += JSUtils.pad(date.millisecond || "0", 3); + break; + + case 'N': + case 'NN': + case 'MMM': + case 'MMMM': + case 'L': + case 'LL': + case 'LLL': + case 'LLLL': + key = templateArr[i] + (date.month || 1); + str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); + break; + + case 'E': + case 'EE': + case 'EEE': + case 'EEEE': + case 'c': + case 'cc': + case 'ccc': + case 'cccc': + key = templateArr[i] + date.getDayOfWeek(); + //console.log("finding " + key + " in the resources"); + str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); + break; + + case 'a': + switch (this.meridiems) { + case "chinese": + if (date.hour < 6) { + key = "azh0"; // before dawn + } else if (date.hour < 9) { + key = "azh1"; // morning + } else if (date.hour < 12) { + key = "azh2"; // late morning/day before noon + } else if (date.hour < 13) { + key = "azh3"; // noon hour/midday + } else if (date.hour < 18) { + key = "azh4"; // afternoon + } else if (date.hour < 21) { + key = "azh5"; // evening time/dusk + } else { + key = "azh6"; // night time + } + break; + case "ethiopic": + if (date.hour < 6) { + key = "a0-ethiopic"; // morning + } else if (date.hour === 6 && date.minute === 0) { + key = "a1-ethiopic"; // noon + } else if (date.hour >= 6 && date.hour < 12) { + key = "a2-ethiopic"; // afternoon + } else if (date.hour >= 12 && date.hour < 18) { + key = "a3-ethiopic"; // evening + } else if (date.hour >= 18) { + key = "a4-ethiopic"; // night + } + break; + default: + key = date.hour < 12 ? "a0" : "a1"; + break; + } + //console.log("finding " + key + " in the resources"); + str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); + break; + + case 'w': + str += date.getWeekOfYear(); + break; + case 'ww': + str += JSUtils.pad(date.getWeekOfYear(), 2); + break; + + case 'D': + str += date.getDayOfYear(); + break; + case 'DD': + str += JSUtils.pad(date.getDayOfYear(), 2); + break; + case 'DDD': + str += JSUtils.pad(date.getDayOfYear(), 3); + break; + case 'W': + str += date.getWeekOfMonth(this.locale); + break; + + case 'G': + key = "G" + date.getEra(); + str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); + break; + + case 'O': + temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); + str += temp.formatChoice(date.day, {num: date.day}); + break; + + case 'z': // general time zone + tz = this.getTimeZone(); // lazy-load the tz + str += tz.getDisplayName(date, "standard"); + break; + case 'Z': // RFC 822 time zone + tz = this.getTimeZone(); // lazy-load the tz + str += tz.getDisplayName(date, "rfc822"); + break; + + default: + str += templateArr[i].replace(/'/g, ""); + break; + } + } + + if (this.digits) { + str = JSUtils.mapString(str, this.digits); + } + return str; + }, + + /** + * Format a particular date instance according to the settings of this + * formatter object. The type of the date instance being formatted must + * correspond exactly to the calendar type with which this formatter was + * constructed. If the types are not compatible, this formatter will + * produce bogus results. + * + * @param {IDate|number|string|Date|JulianDay|null|undefined} dateLike a date-like object to format + * @return {string} the formatted version of the given date instance + */ + format: function (dateLike) { + var thisZoneName = this.tz && this.tz.getId() || "local"; + + var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); + + if (!date.getCalendar || !(date instanceof IDate)) { + throw "Wrong date type passed to DateFmt.format()"; + } + + var dateZoneName = date.timezone || "local"; + + // convert to the time zone of this formatter before formatting + if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { + // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); + // this will recalculate the date components based on the new time zone + // and/or convert a date in another calendar to the current calendar before formatting it + var newDate = DateFactory({ + type: this.calName, + timezone: thisZoneName, + julianday: date.getJulianDay() + }); + + date = newDate; + } + return this._formatTemplate(date, this.templateArr); + }, + + /** + * Return a string that describes a date relative to the given + * reference date. The string returned is text that for the locale that + * was specified when the formatter instance was constructed.

+ * + * The date can be in the future relative to the reference date or in + * the past, and the formatter will generate the appropriate string.

+ * + * The text used to describe the relative reference depends on the length + * of time between the date and the reference. If the time was in the + * past, it will use the "ago" phrase, and in the future, it will use + * the "in" phrase. Examples:

+ * + *

    + *
  • within a minute: either "X seconds ago" or "in X seconds" + *
  • within an hour: either "X minutes ago" or "in X minutes" + *
  • within a day: either "X hours ago" or "in X hours" + *
  • within 2 weeks: either "X days ago" or "in X days" + *
  • within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" + *
  • within two years: either "X months ago" or "in X months" + *
  • longer than 2 years: "X years ago" or "in X years" + *
+ * + * @param {IDate|number|string|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to + * @param {IDate|number|string|Date|JulianDay|null|undefined} date a date being formatted + * @throws "Wrong calendar type" when the start or end dates are not the same + * calendar type as the formatter itself + * @return {string} the formatted relative date + */ + formatRelative: function(reference, date) { + reference = DateFactory._dateToIlib(reference); + date = DateFactory._dateToIlib(date); + + var referenceRd, dateRd, fmt, diff, absDiff, num; + + if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || + typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { + throw "Wrong calendar type"; + } + + referenceRd = reference.getRataDie(); + dateRd = date.getRataDie(); + + diff = referenceRd - dateRd; + absDiff = Math.abs(diff); + + if (absDiff < 0.000694444) { + num = Math.round(absDiff * 86400); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}s ago") : this.sysres.getString("#in {num}s"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 sec ago|#{num} sec ago") : this.sysres.getString("1#in 1 sec|#in {num} sec"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 second ago|#{num} seconds ago") : this.sysres.getString("1#in 1 second|#in {num} seconds"); + break; + } + } else if (absDiff < 0.041666667) { + num = Math.round(absDiff * 1440); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}mi ago") : this.sysres.getString("#in {num}mi"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 min ago|#{num} min ago") : this.sysres.getString("1#in 1 min|#in {num} min"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 minute ago|#{num} minutes ago") : this.sysres.getString("1#in 1 minute|#in {num} minutes"); + break; + } + } else if (absDiff < 1) { + num = Math.round(absDiff * 24); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}h ago") : this.sysres.getString("#in {num}h"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 hr ago|#{num} hrs ago") : this.sysres.getString("1#in 1 hr|#in {num} hrs"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 hour ago|#{num} hours ago") : this.sysres.getString("1#in 1 hour|#in {num} hours"); + break; + } + } else if (absDiff < 14) { + num = Math.round(absDiff); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}d ago") : this.sysres.getString("#in {num}d"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 dy ago|#{nudurationm} dys ago") : this.sysres.getString("1#in 1 dy|#in {num} dys"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 day ago|#{num} days ago") : this.sysres.getString("1#in 1 day|#in {num} days"); + break; + } + } else if (absDiff < 84) { + num = Math.round(absDiff/7); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}w ago") : this.sysres.getString("#in {num}w"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 wk ago|#{num} wks ago") : this.sysres.getString("1#in 1 wk|#in {num} wks"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 week ago|#{num} weeks ago") : this.sysres.getString("1#in 1 week|#in {num} weeks"); + break; + } + } else if (absDiff < 730) { + num = Math.round(absDiff/30.4); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}mo ago") : this.sysres.getString("#in {num}mo"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 mon ago|#{num} mons ago") : this.sysres.getString("1#in 1 mon|#in {num} mons"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 month ago|#{num} months ago") : this.sysres.getString("1#in 1 month|#in {num} months"); + break; + } + } else { + num = Math.round(absDiff/365); + switch (this.length) { + case 's': + fmt = diff > 0 ? this.sysres.getString("#{num}y ago") : this.sysres.getString("#in {num}y"); + break; + case 'm': + fmt = diff > 0 ? this.sysres.getString("1#1 yr ago|#{num} yrs ago") : this.sysres.getString("1#in 1 yr|#in {num} yrs"); + break; + default: + case 'f': + case 'l': + fmt = diff > 0 ? this.sysres.getString("1#1 year ago|#{num} years ago") : this.sysres.getString("1#in 1 year|#in {num} years"); + break; + } + } + return fmt.formatChoice(num, {num: num}); + } }; module.exports = DateFmt; diff --git a/js/lib/DateRngFmt.js b/js/lib/DateRngFmt.js index af773084b1..82fcb4c4e2 100644 --- a/js/lib/DateRngFmt.js +++ b/js/lib/DateRngFmt.js @@ -1,6 +1,6 @@ /* * DateRngFmt.js - Date formatter definition - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data dateformats sysres -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); @@ -39,341 +39,341 @@ var DateFactory = require("./DateFactory.js"); * options. Create different date range formatter instances for different purposes * and then keep them cached for use later if you have more than one range to * format.

- * + * * The options may contain any of the following properties: - * + * *

    - *
  • locale - locale to use when formatting the date/times in the range. If the + *
  • locale - locale to use when formatting the date/times in the range. If the * locale is not specified, then the default locale of the app or web page will be used. - * + * *
  • calendar - the type of calendar to use for this format. The value should * be a sting containing the name of the calendar. Currently, the supported * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the * calendar is not specified, then the default calendar for the locale is used. When the * calendar type is specified, then the format method must be called with an instance of - * the appropriate date type. (eg. Gregorian calendar means that the format method must + * the appropriate date type. (eg. Gregorian calendar means that the format method must * be called with a GregDate instance.) - * + * *
  • timezone - time zone to use when formatting times. This may be a time zone * instance or a time zone specifier string in RFC 822 format. If not specified, the * default time zone for the locale is used. - * - *
  • length - Specify the length of the format to use as a string. The length + * + *
  • length - Specify the length of the format to use as a string. The length * is the approximate size of the formatted string. - * + * *
      *
    • short - use a short representation of the time. This is the most compact format possible for the locale. *
    • medium - use a medium length representation of the time. This is a slightly longer format. - *
    • long - use a long representation of the time. This is a fully specified format, but some of the textual + *
    • long - use a long representation of the time. This is a fully specified format, but some of the textual * components may still be abbreviated. (eg. "Tue" instead of "Tuesday") - *
    • full - use a full representation of the time. This is a fully specified format where all the textual + *
    • full - use a full representation of the time. This is a fully specified format where all the textual * components are spelled out completely. *
    - * - * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be - * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric - * and is longer, the year is 4 digits instead of 2, and the format contains slightly more + * + * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be + * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric + * and is longer, the year is 4 digits instead of 2, and the format contains slightly more * spaces and formatting characters.

    - * + * * Note that the length parameter does not specify which components are to be formatted. The * components that are formatted depend on the length of time in the range. - * + * *

  • clock - specify that formatted times should use a 12 or 24 hour clock if the * format happens to include times. Valid values are "12" and "24".

    - * + * * In some locales, both clocks are used. For example, in en_US, the general populace uses - * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or + * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or * scientific writing, it is more common to use a 24 hour clock. This property allows you to * construct a formatter that overrides the default for the locale.

    - * + * * If this property is not specified, the default is to use the most widely used convention * for the locale. - *

  • onLoad - a callback function to call when the date range format object is fully + *
  • onLoad - a callback function to call when the date range format object is fully * loaded. When the onLoad option is given, the DateRngFmt object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
  • loadParams - an object containing parameters to pass to the + * + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
*

- * - * + * + * * @constructor * @param {Object} options options governing the way this date range formatter instance works */ var DateRngFmt = function(options) { - var sync = true; - var loadParams = undefined; - this.locale = new Locale(); - this.length = "s"; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.calendar) { - this.calName = options.calendar; - } - - if (options.length) { - if (options.length === 'short' || - options.length === 'medium' || - options.length === 'long' || - options.length === 'full') { - // only use the first char to save space in the json files - this.length = options.length.charAt(0); - } - } - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - loadParams = options.loadParams; - } - - var opts = {}; - JSUtils.shallowCopy(options, opts); - opts.sync = sync; - opts.loadParams = loadParams; - - /** - * @private - */ - opts.onLoad = ilib.bind(this, function (fmt) { - this.dateFmt = fmt; - if (fmt) { - this.locinfo = this.dateFmt.locinfo; - - // get the default calendar name from the locale, and if the locale doesn't define - // one, use the hard-coded gregorian as the last resort - this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; - CalendarFactory({ - type: this.calName, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function(cal) { - this.cal = cal; - - if (!this.cal) { - // always synchronous - this.cal = new GregorianCal(); - } - - this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; - this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - if (options && typeof(options.sync) === "boolean" && !options.sync && typeof(options.onLoad) === 'function') { + var sync = true; + var loadParams = undefined; + this.locale = new Locale(); + this.length = "s"; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.calendar) { + this.calName = options.calendar; + } + + if (options.length) { + if (options.length === 'short' || + options.length === 'medium' || + options.length === 'long' || + options.length === 'full') { + // only use the first char to save space in the json files + this.length = options.length.charAt(0); + } + } + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + loadParams = options.loadParams; + } + + var opts = {}; + JSUtils.shallowCopy(options, opts); + opts.sync = sync; + opts.loadParams = loadParams; + + /** + * @private + */ + opts.onLoad = ilib.bind(this, function (fmt) { + this.dateFmt = fmt; + if (fmt) { + this.locinfo = this.dateFmt.locinfo; + + // get the default calendar name from the locale, and if the locale doesn't define + // one, use the hard-coded gregorian as the last resort + this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; + CalendarFactory({ + type: this.calName, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function(cal) { + this.cal = cal; + + if (!this.cal) { + // always synchronous + this.cal = new GregorianCal(); + } + + this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; + this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + if (options && typeof(options.sync) === "boolean" && !options.sync && typeof(options.onLoad) === 'function') { options.onLoad(undefined); } else { throw "No formats available for calendar " + this.calName + " in locale " + this.locale.getSpec(); } - } - }); + } + }); - // delegate a bunch of the formatting to this formatter - new DateFmt(opts); + // delegate a bunch of the formatting to this formatter + new DateFmt(opts); }; DateRngFmt.prototype = { - /** - * Return the locale used with this formatter instance. - * @return {Locale} the Locale instance for this formatter - */ - getLocale: function() { - return this.locale; - }, - - /** - * Return the name of the calendar used to format date/times for this - * formatter instance. - * @return {string} the name of the calendar used by this formatter - */ - getCalendar: function () { - return this.dateFmt.getCalendar(); - }, - - /** - * Return the length used to format date/times in this formatter. This is either the - * value of the length option to the constructor, or the default value. - * - * @return {string} the length of formats this formatter returns - */ - getLength: function () { - return DateFmt.lenmap[this.length] || ""; - }, - - /** - * Return the time zone used to format date/times for this formatter - * instance. - * @return {TimeZone} a string naming the time zone - */ - getTimeZone: function () { - return this.dateFmt.getTimeZone(); - }, - - /** - * Return the clock option set in the constructor. If the clock option was - * not given, the default from the locale is returned instead. - * @return {string} "12" or "24" depending on whether this formatter uses - * the 12-hour or 24-hour clock - */ - getClock: function () { - return this.dateFmt.getClock(); - }, - - /** - * Format a date/time range according to the settings of the current - * formatter. The range is specified as being from the "start" date until - * the "end" date.

- * - * The template that the date/time range uses depends on the - * length of time between the dates, on the premise that a long date range - * which is too specific is not useful. For example, when giving - * the dates of the 100 Years War, in most situations it would be more - * appropriate to format the range as "1337 - 1453" than to format it as - * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format - * is much too specific given the length of time that the range represents. - * If a very specific, but long, date range really is needed, the caller - * should format two specific dates separately and put them - * together as you might with other normal strings.

- * - * The format used for a date range contains the following date components, - * where the order of those components is rearranged and the component values - * are translated according to each locale: - * - *

    - *
  • within 3 days: the times of day, dates, months, and years - *
  • within 730 days (2 years): the dates, months, and years - *
  • within 3650 days (10 years): the months and years - *
  • longer than 10 years: the years only - *
- * - * In general, if any of the date components share a value between the - * start and end date, that component is only given once. For example, - * if the range is from November 15, 2011 to November 26, 2011, the - * start and end dates both share the same month and year. The - * range would then be formatted as "November 15-26, 2011".

- * - * If you want to format a length of time instead of a particular range of - * time (for example, the length of an event rather than the specific start time - * and end time of that event), then use a duration formatter instance - * (DurationFmt) instead. The formatRange method will make sure that each component - * of the date/time is within the normal range for that component. For example, - * the minutes will always be between 0 and 59, no matter what is specified in - * the date to format, because that is the normal range for minutes. A duration - * format will allow the number of minutes to exceed 59. For example, if you - * were displaying the length of a movie that is 198 minutes long, the minutes - * component of a duration could be 198.

- * - * @param {IDate|Date|number|string} startDateLike the starting date/time of the range. The - * date may be given as an ilib IDate object, a javascript intrinsic Date object, a - * unix time, or a date string parsable by the javscript Date. - * @param {IDate|Date|number|string} endDateLike the ending date/time of the range. The - * date may be given as an ilib IDate object, a javascript intrinsic Date object, a - * unix time, or a date string parsable by the javscript Date. - * @throws "Wrong calendar type" when the start or end dates are not the same - * calendar type as the formatter itself - * @return {string} a date range formatted for the locale - */ - format: function (startDateLike, endDateLike) { - var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate, formats; - var thisZoneName = this.dateFmt.tz && this.dateFmt.tz.getId() || "local"; - - var start = DateFactory._dateToIlib(startDateLike, thisZoneName, this.locale); - var end = DateFactory._dateToIlib(endDateLike, thisZoneName, this.locale); - - if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || - typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { - throw "Wrong calendar type"; - } - - startRd = start.getRataDie(); - endRd = end.getRataDie(); - - // - // legend: - // c00 - difference is less than 3 days. Year, month, and date are same, but time is different - // c01 - difference is less than 3 days. Year and month are same but date and time are different - // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) - // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) - // c10 - difference is less than 2 years. Year and month are the same, but date is different. - // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. - // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) - // c20 - difference is less than 10 years. All fields are different. - // c30 - difference is more than 10 years. All fields are different. - // - - if (endRd - startRd < 3) { - if (start.year === end.year) { - if (start.month === end.month) { - if (start.day === end.day) { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); - } - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); - } - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); - } - } else if (endRd - startRd < 730) { - if (start.year === end.year) { - if (start.month === end.month) { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); - } - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); - } - } else if (endRd - startRd < 3650) { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); - } else { - fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); - } - - formats = this.dateFmt.formats.date; - yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "y", this.length) || "yyyy"); - monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "m", this.length) || "MM"); - dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "d", this.length) || "dd"); - - /* - console.log("fmt is " + fmt.toString()); - console.log("year template is " + yearTemplate); - console.log("month template is " + monthTemplate); - console.log("day template is " + dayTemplate); - */ - - return fmt.format({ - sy: this.dateFmt._formatTemplate(start, yearTemplate), - sm: this.dateFmt._formatTemplate(start, monthTemplate), - sd: this.dateFmt._formatTemplate(start, dayTemplate), - st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), - ey: this.dateFmt._formatTemplate(end, yearTemplate), - em: this.dateFmt._formatTemplate(end, monthTemplate), - ed: this.dateFmt._formatTemplate(end, dayTemplate), - et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) - }); - } + /** + * Return the locale used with this formatter instance. + * @return {Locale} the Locale instance for this formatter + */ + getLocale: function() { + return this.locale; + }, + + /** + * Return the name of the calendar used to format date/times for this + * formatter instance. + * @return {string} the name of the calendar used by this formatter + */ + getCalendar: function () { + return this.dateFmt.getCalendar(); + }, + + /** + * Return the length used to format date/times in this formatter. This is either the + * value of the length option to the constructor, or the default value. + * + * @return {string} the length of formats this formatter returns + */ + getLength: function () { + return DateFmt.lenmap[this.length] || ""; + }, + + /** + * Return the time zone used to format date/times for this formatter + * instance. + * @return {TimeZone} a string naming the time zone + */ + getTimeZone: function () { + return this.dateFmt.getTimeZone(); + }, + + /** + * Return the clock option set in the constructor. If the clock option was + * not given, the default from the locale is returned instead. + * @return {string} "12" or "24" depending on whether this formatter uses + * the 12-hour or 24-hour clock + */ + getClock: function () { + return this.dateFmt.getClock(); + }, + + /** + * Format a date/time range according to the settings of the current + * formatter. The range is specified as being from the "start" date until + * the "end" date.

+ * + * The template that the date/time range uses depends on the + * length of time between the dates, on the premise that a long date range + * which is too specific is not useful. For example, when giving + * the dates of the 100 Years War, in most situations it would be more + * appropriate to format the range as "1337 - 1453" than to format it as + * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format + * is much too specific given the length of time that the range represents. + * If a very specific, but long, date range really is needed, the caller + * should format two specific dates separately and put them + * together as you might with other normal strings.

+ * + * The format used for a date range contains the following date components, + * where the order of those components is rearranged and the component values + * are translated according to each locale: + * + *

    + *
  • within 3 days: the times of day, dates, months, and years + *
  • within 730 days (2 years): the dates, months, and years + *
  • within 3650 days (10 years): the months and years + *
  • longer than 10 years: the years only + *
+ * + * In general, if any of the date components share a value between the + * start and end date, that component is only given once. For example, + * if the range is from November 15, 2011 to November 26, 2011, the + * start and end dates both share the same month and year. The + * range would then be formatted as "November 15-26, 2011".

+ * + * If you want to format a length of time instead of a particular range of + * time (for example, the length of an event rather than the specific start time + * and end time of that event), then use a duration formatter instance + * (DurationFmt) instead. The formatRange method will make sure that each component + * of the date/time is within the normal range for that component. For example, + * the minutes will always be between 0 and 59, no matter what is specified in + * the date to format, because that is the normal range for minutes. A duration + * format will allow the number of minutes to exceed 59. For example, if you + * were displaying the length of a movie that is 198 minutes long, the minutes + * component of a duration could be 198.

+ * + * @param {IDate|Date|number|string} startDateLike the starting date/time of the range. The + * date may be given as an ilib IDate object, a javascript intrinsic Date object, a + * unix time, or a date string parsable by the javscript Date. + * @param {IDate|Date|number|string} endDateLike the ending date/time of the range. The + * date may be given as an ilib IDate object, a javascript intrinsic Date object, a + * unix time, or a date string parsable by the javscript Date. + * @throws "Wrong calendar type" when the start or end dates are not the same + * calendar type as the formatter itself + * @return {string} a date range formatted for the locale + */ + format: function (startDateLike, endDateLike) { + var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate, formats; + var thisZoneName = this.dateFmt.tz && this.dateFmt.tz.getId() || "local"; + + var start = DateFactory._dateToIlib(startDateLike, thisZoneName, this.locale); + var end = DateFactory._dateToIlib(endDateLike, thisZoneName, this.locale); + + if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || + typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { + throw "Wrong calendar type"; + } + + startRd = start.getRataDie(); + endRd = end.getRataDie(); + + // + // legend: + // c00 - difference is less than 3 days. Year, month, and date are same, but time is different + // c01 - difference is less than 3 days. Year and month are same but date and time are different + // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) + // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) + // c10 - difference is less than 2 years. Year and month are the same, but date is different. + // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. + // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) + // c20 - difference is less than 10 years. All fields are different. + // c30 - difference is more than 10 years. All fields are different. + // + + if (endRd - startRd < 3) { + if (start.year === end.year) { + if (start.month === end.month) { + if (start.day === end.day) { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); + } + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); + } + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); + } + } else if (endRd - startRd < 730) { + if (start.year === end.year) { + if (start.month === end.month) { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); + } + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); + } + } else if (endRd - startRd < 3650) { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); + } else { + fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); + } + + formats = this.dateFmt.formats.date; + yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "y", this.length) || "yyyy"); + monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "m", this.length) || "MM"); + dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "d", this.length) || "dd"); + + /* + console.log("fmt is " + fmt.toString()); + console.log("year template is " + yearTemplate); + console.log("month template is " + monthTemplate); + console.log("day template is " + dayTemplate); + */ + + return fmt.format({ + sy: this.dateFmt._formatTemplate(start, yearTemplate), + sm: this.dateFmt._formatTemplate(start, monthTemplate), + sd: this.dateFmt._formatTemplate(start, dayTemplate), + st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), + ey: this.dateFmt._formatTemplate(end, yearTemplate), + em: this.dateFmt._formatTemplate(end, monthTemplate), + ed: this.dateFmt._formatTemplate(end, dayTemplate), + et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) + }); + } }; module.exports = DateRngFmt; diff --git a/js/lib/DigitalStorageUnit.js b/js/lib/DigitalStorageUnit.js index aaa5c5186b..76fa6521e3 100644 --- a/js/lib/DigitalStorageUnit.js +++ b/js/lib/DigitalStorageUnit.js @@ -36,8 +36,8 @@ var JSUtils = require("./JSUtils.js"); * the construction of this instance */ var DigitalStorageUnit = function (options) { - this.unit = "byte"; - this.amount = 0; + this.unit = "byte"; + this.amount = 0; this.ratios = DigitalStorageUnit.ratios; this.aliases = DigitalStorageUnit.aliases; @@ -53,7 +53,7 @@ DigitalStorageUnit.prototype.constructor = DigitalStorageUnit; DigitalStorageUnit.ratios = { /* # bit byte kb kB mb mB gb gB tb tB pb pB */ - "bit": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16 ], + "bit": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16 ], "byte": [ 2, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 7.105427358e-15, 8.881784197e-16 ], "kilobit": [ 3, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13 ], "kilobyte": [ 4, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13 ], @@ -69,7 +69,7 @@ DigitalStorageUnit.ratios = { /** * Return a new instance of this type of measurement. - * + * * @param {Object} params parameters to the constructor * @return {Measurement} a measurement subclass instance */ @@ -118,7 +118,7 @@ DigitalStorageUnit.byteSystem = [ * @return {string} the name of the type of this measurement */ DigitalStorageUnit.prototype.getMeasure = function() { - return "digitalStorage"; + return "digitalStorage"; }; /** @@ -165,7 +165,7 @@ DigitalStorageUnit.prototype.scale = function(measurementsystem, units) { mSystem = DigitalStorageUnit.bitSystem; } } - + return this.newUnit(this.scaleUnits(mSystem)); }; @@ -333,12 +333,12 @@ DigitalStorageUnit.aliases = { DigitalStorageUnit.convert = function(to, from, digitalStorage) { from = Measurement.getUnitIdCaseInsensitive(DigitalStorageUnit, from) || from; to = Measurement.getUnitIdCaseInsensitive(DigitalStorageUnit, to) || to; - var fromRow = DigitalStorageUnit.ratios[from]; - var toRow = DigitalStorageUnit.ratios[to]; - if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { - return undefined; - } - var result = digitalStorage * fromRow[toRow[0]]; + var fromRow = DigitalStorageUnit.ratios[from]; + var toRow = DigitalStorageUnit.ratios[to]; + if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { + return undefined; + } + var result = digitalStorage * fromRow[toRow[0]]; return result; }; diff --git a/js/lib/DurationFmt.js b/js/lib/DurationFmt.js index b08e256e03..d8c02e6337 100644 --- a/js/lib/DurationFmt.js +++ b/js/lib/DurationFmt.js @@ -19,7 +19,7 @@ // !data dateformats sysres -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); @@ -35,102 +35,102 @@ var ScriptInfo = require("./ScriptInfo.js"); * options. Create different duration formatter instances for different purposes * and then keep them cached for use later if you have more than one duration to * format.

- * - * Duration formatters format lengths of time. The duration formatter is meant to format - * durations of such things as the length of a song or a movie or a meeting, or the - * current position in that song or movie while playing it. If you wish to format a + * + * Duration formatters format lengths of time. The duration formatter is meant to format + * durations of such things as the length of a song or a movie or a meeting, or the + * current position in that song or movie while playing it. If you wish to format a * period of time that has a specific start and end date/time, then use a * [DateRngFmt] instance instead and call its format method.

- * + * * The options may contain any of the following properties: - * + * *

    *
  • locale - locale to use when formatting the duration. If the locale is * not specified, then the default locale of the app or web page will be used. - * - *
  • length - Specify the length of the format to use. The length is the approximate size of the + * + *
  • length - Specify the length of the format to use. The length is the approximate size of the * formatted string. - * + * *
      *
    • short - use a short representation of the duration. This is the most compact format possible for the locale. eg. 1y 1m 1w 1d 1:01:01 *
    • medium - use a medium length representation of the duration. This is a slightly longer format. eg. 1 yr 1 mo 1 wk 1 dy 1 hr 1 mi 1 se - *
    • long - use a long representation of the duration. This is a fully specified format, but some of the textual + *
    • long - use a long representation of the duration. This is a fully specified format, but some of the textual * parts may still be abbreviated. eg. 1 yr 1 mo 1 wk 1 day 1 hr 1 min 1 sec - *
    • full - use a full representation of the duration. This is a fully specified format where all the textual + *
    • full - use a full representation of the duration. This is a fully specified format where all the textual * parts are spelled out completely. eg. 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute and 1 second *
    - * + * *
  • style - whether hours, minutes, and seconds should be formatted as a text string * or as a regular time as on a clock. eg. text is "1 hour, 15 minutes", whereas clock is "1:15:00". Valid * values for this property are "text" or "clock". Default if this property is not specified * is "text". - * - *
  • useNative - the flag used to determaine whether to use the native script settings + * + *
  • useNative - the flag used to determaine whether to use the native script settings * for formatting the numbers . - * - *
  • onLoad - a callback function to call when the format data is fully + * + *
  • onLoad - a callback function to call when the format data is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
  • loadParams - an object containing parameters to pass to the + * + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
*

- * - * + * + * * @constructor * @param {?Object} options options governing the way this date formatter instance works */ var DurationFmt = function(options) { - var sync = true; - var loadParams = undefined; - - this.locale = new Locale(); - this.length = "short"; - this.style = "text"; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.length) { - if (options.length === 'short' || - options.length === 'medium' || - options.length === 'long' || - options.length === 'full') { - this.length = options.length; - } - } - - if (options.style) { - if (options.style === 'text' || options.style === 'clock') { - this.style = options.style; - } - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.useNative) === 'boolean') { - this.useNative = options.useNative; - } - - loadParams = options.loadParams; - } - options = options || {sync: true}; + var sync = true; + var loadParams = undefined; + + this.locale = new Locale(); + this.length = "short"; + this.style = "text"; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.length) { + if (options.length === 'short' || + options.length === 'medium' || + options.length === 'long' || + options.length === 'full') { + this.length = options.length; + } + } + + if (options.style) { + if (options.style === 'text' || options.style === 'clock') { + this.style = options.style; + } + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.useNative) === 'boolean') { + this.useNative = options.useNative; + } + + loadParams = options.loadParams; + } + options = options || {sync: true}; new LocaleInfo(this.locale, { sync: sync, @@ -269,18 +269,18 @@ var DurationFmt = function(options) { * @static */ DurationFmt.complist = { - "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], - "clock": ["year", "month", "week", "day"] + "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], + "clock": ["year", "month", "week", "day"] }; /** * @private */ DurationFmt.prototype._mapDigits = function(str) { - if (this.useNative && this.digits) { - return JSUtils.mapString(str.toString(), this.digits); - } - return str; + if (this.useNative && this.digits) { + return JSUtils.mapString(str.toString(), this.digits); + } + return str; }; /** @@ -305,7 +305,7 @@ DurationFmt.prototype._init = function(locinfo, options) { } } } else if (locinfo.getDigitsStyle() === "native") { - // else if the locale usually uses native digits, then use them + // else if the locale usually uses native digits, then use them digits = locinfo.getNativeDigits(); if (digits) { this.useNative = true; @@ -315,17 +315,17 @@ DurationFmt.prototype._init = function(locinfo, options) { if (typeof(options.onLoad) === 'function') { options.onLoad(this); - } + } }) }); }; /** * Format a duration according to the format template of this formatter instance.

- * - * The components parameter should be an object that contains any or all of these + * + * The components parameter should be an object that contains any or all of these * numeric properties: - * + * *

* - * Here are some examples of converting a length into new units. - * The first method is via this factory function by passing the old measurement + * Here are some examples of converting a length into new units. + * The first method is via this factory function by passing the old measurement * in as the "amount" property.

- * + * *

  * var measurement1 = MeasurementFactory({
  *   amount: 5,
@@ -91,11 +91,11 @@ var Measurement = require("./Measurement.js");
  *   units: "miles"
  * });
  * 
- * + * * The value in measurement2 will end up being about 3.125 miles.

- * + * * The second method uses the convert method.

- * + * *

  * var measurement1 = MeasurementFactory({
  *   amount: 5,
@@ -106,7 +106,7 @@ var Measurement = require("./Measurement.js");
  * 
* * The value in measurement2 will again end up being about 3.125 miles. - * + * * @static * @param {Object=} options options that control the construction of this instance */ @@ -154,16 +154,16 @@ var MeasurementFactory = function(options) { * are case-insensitive. * * @static - * @return {Array.} an array of strings containing names of measurement + * @return {Array.} an array of strings containing names of measurement * units available */ MeasurementFactory.getAvailableUnits = function () { - var units = []; - for (var c in Measurement._constructors) { - var measure = Measurement._constructors[c]; - units = units.concat(measure.getMeasures()); - } - return units; + var units = []; + for (var c in Measurement._constructors) { + var measure = Measurement._constructors[c]; + units = units.concat(measure.getMeasures()); + } + return units; }; module.exports = MeasurementFactory; diff --git a/js/lib/Name.js b/js/lib/Name.js index d994dc745a..97da7d212c 100644 --- a/js/lib/Name.js +++ b/js/lib/Name.js @@ -25,7 +25,7 @@ // http://www.mentalfloss.com/blogs/archives/59277 // other countries with first name restrictions: Norway, China, New Zealand, Japan, Sweden, Germany, Hungary -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -60,7 +60,7 @@ var isSpace = require("./isSpace.js"); * and family name. *
  • compoundFamilyName - for Asian and some other types of names, search for compound * family names. If this parameter is not specified, the default is to use the setting that is - * most common for the locale. If it is specified, the locale default is + * most common for the locale. If it is specified, the locale default is * overridden with this flag. *
  • onLoad - a callback function to call when the name info is fully * loaded and the name has been parsed. When the onLoad option is given, the name object @@ -80,17 +80,17 @@ var isSpace = require("./isSpace.js"); * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. * - * - * Additionally, a name instance can be constructed by giving the explicit + * + * Additionally, a name instance can be constructed by giving the explicit * already-parsed fields or by using another name instance as the parameter. (That is, * it becomes a copy constructor.) The name fields can be any of the following: - * + * *
      *
    • prefix - the prefix prepended to the name *
    • givenName - the person's given or "first" name *
    • middleName - one or more middle names, separated by spaces even if the * language doesn't use usually use spaces. The spaces are merely separators. - *
    • familyName - one or more family or "last" names, separated by spaces + *
    • familyName - one or more family or "last" names, separated by spaces * even if the language doesn't use usually use spaces. The spaces are merely separators. *
    • suffix - the suffix appended to the name *
    • honorific - the honorific title of the name. This could be formatted @@ -98,7 +98,7 @@ var isSpace = require("./isSpace.js"); * should only give either an honorific or a prefix/suffix, but not both. *
    * - * When the parser has completed its parsing, it fills in the same fields as listed + * When the parser has completed its parsing, it fills in the same fields as listed * above.

    * * For names that include auxilliary words, such as the family name "van der Heijden", all @@ -145,193 +145,193 @@ var Name = function (name, options) { if (typeof(options.loadParams) !== 'undefined') { this.loadParams = options.loadParams; } - + if (typeof(options.compoundFamilyName) !== 'undefined') { - this.singleFamilyName = !options.compoundFamilyName; + this.singleFamilyName = !options.compoundFamilyName; } } this.locale = this.locale || new Locale(); - - isAlpha._init(sync, this.loadParams, ilib.bind(this, function() { - isIdeo._init(sync, this.loadParams, ilib.bind(this, function() { - isPunct._init(sync, this.loadParams, ilib.bind(this, function() { - isSpace._init(sync, this.loadParams, ilib.bind(this, function() { - Utils.loadData({ - object: "Name", - locale: this.locale, - name: "name.json", - sync: sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (info) { - if (!info) { - info = Name.defaultInfo[this.style || "western"]; - } + + isAlpha._init(sync, this.loadParams, ilib.bind(this, function() { + isIdeo._init(sync, this.loadParams, ilib.bind(this, function() { + isPunct._init(sync, this.loadParams, ilib.bind(this, function() { + isSpace._init(sync, this.loadParams, ilib.bind(this, function() { + Utils.loadData({ + object: "Name", + locale: this.locale, + name: "name.json", + sync: sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (info) { + if (!info) { + info = Name.defaultInfo[this.style || "western"]; + } if (typeof (name) === 'object') { - // copy constructor - /** - * The prefixes for this name - * @type {string|Array.} - */ - this.prefix = name.prefix; - /** - * The given (personal) name in this name. - * @type {string|Array.} - */ - this.givenName = name.givenName; - /** - * The middle names used in this name. If there are multiple middle names, they all - * appear in this field separated by spaces. - * @type {string|Array.} - */ - this.middleName = name.middleName; - /** - * The family names in this name. If there are multiple family names, they all - * appear in this field separated by spaces. - * @type {string|Array.} - */ - this.familyName = name.familyName; - /** - * The suffixes for this name. If there are multiple suffixes, they all - * appear in this field separated by spaces. - * @type {string|Array.} - */ - this.suffix = name.suffix; - /** - * The honorific title for this name. This honorific will be used as the prefix - * or suffix as dictated by the locale - * @type {string|Array.} - */ - this.honorific = name.honorific; - - // private properties - this.locale = name.locale; - this.style = name.style; - this.order = name.order; - this.useSpaces = name.useSpaces; - this.isAsianName = name.isAsianName; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - - return; - } - /** - * @type {{ - * nameStyle:string, - * order:string, - * prefixes:Array., - * suffixes:Array., - * auxillaries:Array., - * honorifics:Array., - * knownFamilyNames:Array., - * noCompoundFamilyNames:boolean, - * sortByHeadWord:boolean - * }} */ - this.info = info; - this._init(name); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - })); - })); - })); - })); + // copy constructor + /** + * The prefixes for this name + * @type {string|Array.} + */ + this.prefix = name.prefix; + /** + * The given (personal) name in this name. + * @type {string|Array.} + */ + this.givenName = name.givenName; + /** + * The middle names used in this name. If there are multiple middle names, they all + * appear in this field separated by spaces. + * @type {string|Array.} + */ + this.middleName = name.middleName; + /** + * The family names in this name. If there are multiple family names, they all + * appear in this field separated by spaces. + * @type {string|Array.} + */ + this.familyName = name.familyName; + /** + * The suffixes for this name. If there are multiple suffixes, they all + * appear in this field separated by spaces. + * @type {string|Array.} + */ + this.suffix = name.suffix; + /** + * The honorific title for this name. This honorific will be used as the prefix + * or suffix as dictated by the locale + * @type {string|Array.} + */ + this.honorific = name.honorific; + + // private properties + this.locale = name.locale; + this.style = name.style; + this.order = name.order; + this.useSpaces = name.useSpaces; + this.isAsianName = name.isAsianName; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + + return; + } + /** + * @type {{ + * nameStyle:string, + * order:string, + * prefixes:Array., + * suffixes:Array., + * auxillaries:Array., + * honorifics:Array., + * knownFamilyNames:Array., + * noCompoundFamilyNames:boolean, + * sortByHeadWord:boolean + * }} */ + this.info = info; + this._init(name); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + })); + })); + })); + })); }; Name.defaultInfo = { - "western": ilib.data.name || { - "components": { - "short": "gf", - "medium": "gmf", - "long": "pgmf", - "full": "pgmfs", - "formal_short": "hf", - "formal_long": "hgf" - }, - "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", - "sortByHeadWord": false, - "nameStyle": "western", - "conjunctions": { - "and1": "and", - "and2": "and", - "or1": "or", - "or2": "or" - }, - "auxillaries": { - "von": 1, - "von der": 1, - "von den": 1, - "van": 1, - "van der": 1, - "van de": 1, - "van den": 1, - "de": 1, - "di": 1, - "la": 1, - "lo": 1, - "des": 1, - "le": 1, - "les": 1, - "du": 1, - "de la": 1, - "del": 1, - "de los": 1, - "de las": 1 - }, - "prefixes": [ - "doctor", - "dr", - "mr", - "mrs", - "ms", - "mister", - "madame", - "madamoiselle", - "miss", - "monsieur", - "señor", - "señora", - "señorita" - ], - "suffixes": [ - ",", - "junior", - "jr", - "senior", - "sr", - "i", - "ii", - "iii", - "esq", - "phd", - "md" - ], - "patronymicName":[ ], - "familyNames":[ ] - }, - "asian": { - "components": { - "short": "gf", - "medium": "gmf", - "long": "hgmf", - "full": "hgmf", - "formal_short": "hf", - "formal_long": "hgf" - }, - "format": "{prefix}{familyName}{middleName}{givenName}{suffix}", - "nameStyle": "asian", - "sortByHeadWord": false, - "conjunctions": {}, - "auxillaries": {}, - "prefixes": [], - "suffixes": [], - "patronymicName":[], - "familyNames":[] - } + "western": ilib.data.name || { + "components": { + "short": "gf", + "medium": "gmf", + "long": "pgmf", + "full": "pgmfs", + "formal_short": "hf", + "formal_long": "hgf" + }, + "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", + "sortByHeadWord": false, + "nameStyle": "western", + "conjunctions": { + "and1": "and", + "and2": "and", + "or1": "or", + "or2": "or" + }, + "auxillaries": { + "von": 1, + "von der": 1, + "von den": 1, + "van": 1, + "van der": 1, + "van de": 1, + "van den": 1, + "de": 1, + "di": 1, + "la": 1, + "lo": 1, + "des": 1, + "le": 1, + "les": 1, + "du": 1, + "de la": 1, + "del": 1, + "de los": 1, + "de las": 1 + }, + "prefixes": [ + "doctor", + "dr", + "mr", + "mrs", + "ms", + "mister", + "madame", + "madamoiselle", + "miss", + "monsieur", + "señor", + "señora", + "señorita" + ], + "suffixes": [ + ",", + "junior", + "jr", + "senior", + "sr", + "i", + "ii", + "iii", + "esq", + "phd", + "md" + ], + "patronymicName":[ ], + "familyNames":[ ] + }, + "asian": { + "components": { + "short": "gf", + "medium": "gmf", + "long": "hgmf", + "full": "hgmf", + "formal_short": "hf", + "formal_long": "hgf" + }, + "format": "{prefix}{familyName}{middleName}{givenName}{suffix}", + "nameStyle": "asian", + "sortByHeadWord": false, + "conjunctions": {}, + "auxillaries": {}, + "prefixes": [], + "suffixes": [], + "patronymicName":[], + "familyNames":[] + } }; /** @@ -341,10 +341,10 @@ Name.defaultInfo = { * @protected */ Name._isAsianChar = function(c) { - return isIdeo(c) || - CType.withinRange(c, "hangul") || - CType.withinRange(c, "hiragana") || - CType.withinRange(c, "katakana"); + return isIdeo(c) || + CType.withinRange(c, "hangul") || + CType.withinRange(c, "hiragana") || + CType.withinRange(c, "katakana"); }; @@ -362,7 +362,7 @@ Name._isAsianName = function (name, language) { if (name && name.length > 0) { for (i = 0; i < name.length; i++) { - var c = name.charAt(i); + var c = name.charAt(i); if (Name._isAsianChar(c)) { if (language =="ko" || language =="ja" || language =="zh") { @@ -446,7 +446,7 @@ Name.prototype = { } parts = name.trim().split(''); } - //} + //} else { name = name.replace(/, /g, ' , '); name = name.replace(/\s+/g, ' '); // compress multiple whitespaces @@ -517,36 +517,36 @@ Name.prototype = { }, /** - * @return {number} - * - _findSequence: function(parts, hash, isAsian) { - var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; - - if (parts.length > 0 && hash) { - //console.info("_findSequence: finding sequences"); - for (var start = 0; start < parts.length-1; start++) { - for ( i = parts.length; i > start; i-- ) { - sequenceArray = parts.slice(start, i); - sequence = sequenceArray.join(isAsian ? '' : ' '); - sequenceLower = sequence.toLowerCase(); - sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods - - //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); - - if ( sequenceLower in hash ) { - ret.match = sequenceArray; - ret.start = start; - ret.end = i; - return ret; - //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); - } - } - } - } - - return undefined; - }, - */ + * @return {number} + * + _findSequence: function(parts, hash, isAsian) { + var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; + + if (parts.length > 0 && hash) { + //console.info("_findSequence: finding sequences"); + for (var start = 0; start < parts.length-1; start++) { + for ( i = parts.length; i > start; i-- ) { + sequenceArray = parts.slice(start, i); + sequence = sequenceArray.join(isAsian ? '' : ' '); + sequenceLower = sequence.toLowerCase(); + sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods + + //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); + + if ( sequenceLower in hash ) { + ret.match = sequenceArray; + ret.start = start; + ret.end = i; + return ret; + //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); + } + } + } + } + + return undefined; + }, + */ /** * @protected @@ -568,8 +568,8 @@ Name.prototype = { if (prefixLower in names) { aux = aux.concat(isAsian ? prefix : prefixArray); if (noCompoundPrefix) { - // don't need to parse further. Just return it as is. - return aux; + // don't need to parse further. Just return it as is. + return aux; } parts = parts.slice(i); i = parts.length + 1; @@ -754,7 +754,7 @@ Name.prototype = { if (familyNameArray && familyNameArray.length > 0) { this.familyName = familyNameArray.join(''); this.givenName = parts.slice(this.familyName.length).join(''); - + //Overide parsing rules if spaces are found in korean if (language === "ko" && tempFullName.search(/\s*[/\s]/) > -1 && !this.suffix) { this._parseKoreanName(tempFullName); @@ -787,52 +787,52 @@ Name.prototype = { this.middleName = tempName.slice(fistSpaceIndex, lastSpaceIndex); this.givenName = tempName.slice(lastSpaceIndex, tempName.length); } - + }, /** * @protected */ _parseJapaneseName: function (parts) { - if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { - if (parts.length === 1) { - if (CType.withinRange(parts[0], "cjk")) { - this.familyName = parts[0]; - } else { - this.givenName = parts[0]; - } - return; - } else if (parts.length === 2) { - this.familyName = parts.slice(0,parts.length).join("") - return; - } - } - if (parts.length > 1) { - var fn = ""; - for (var i = 0; i < parts.length; i++) { - if (CType.withinRange(parts[i], "cjk")) { - fn += parts[i]; - } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { - this.familyName = fn; - this.givenName = parts.slice(i,parts.length).join(""); - return; - } else { - break; - } - } - } - if (parts.length === 1) { - this.familyName = parts[0]; - } else if (parts.length === 2) { - this.familyName = parts[0]; - this.givenName = parts[1]; - } else if (parts.length === 3) { - this.familyName = parts[0]; - this.givenName = parts.slice(1,parts.length).join(""); - } else if (parts.length > 3) { - this.familyName = parts.slice(0,2).join("") - this.givenName = parts.slice(2,parts.length).join(""); - } + if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { + if (parts.length === 1) { + if (CType.withinRange(parts[0], "cjk")) { + this.familyName = parts[0]; + } else { + this.givenName = parts[0]; + } + return; + } else if (parts.length === 2) { + this.familyName = parts.slice(0,parts.length).join("") + return; + } + } + if (parts.length > 1) { + var fn = ""; + for (var i = 0; i < parts.length; i++) { + if (CType.withinRange(parts[i], "cjk")) { + fn += parts[i]; + } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { + this.familyName = fn; + this.givenName = parts.slice(i,parts.length).join(""); + return; + } else { + break; + } + } + } + if (parts.length === 1) { + this.familyName = parts[0]; + } else if (parts.length === 2) { + this.familyName = parts[0]; + this.givenName = parts[1]; + } else if (parts.length === 3) { + this.familyName = parts[0]; + this.givenName = parts.slice(1,parts.length).join(""); + } else if (parts.length > 3) { + this.familyName = parts.slice(0,2).join("") + this.givenName = parts.slice(2,parts.length).join(""); + } }, /** @@ -867,7 +867,7 @@ Name.prototype = { conjunctionIndex = this._findLastConjunction(parts); ////console.log("@@@@@@@@@@@@@@@@"+conjunctionIndex) if (conjunctionIndex > 0) { - // if there's a conjunction that's not the first token, put everything up to and + // if there's a conjunction that's not the first token, put everything up to and // including the token after it into the first name, the last 2 tokens into // the family name (if they exist) and everything else in to the middle name // 0 1 2 3 4 5 @@ -919,7 +919,7 @@ Name.prototype = { conjunctionIndex = this._findLastConjunction(parts); if (conjunctionIndex > 0) { - // if there's a conjunction that's not the first token, put everything up to and + // if there's a conjunction that's not the first token, put everything up to and // including the token after it into the first name, the last 2 tokens into // the family name (if they exist) and everything else in to the middle name // 0 1 2 3 4 5 @@ -940,7 +940,7 @@ Name.prototype = { this.middleName = parts; } //} else if (parts.length === 1) { - // this.familyName = parts[0]; + // this.familyName = parts[0]; //} } else { this.givenName = parts.splice(0, 1); @@ -949,12 +949,12 @@ Name.prototype = { } } }, - + /** * @protected */ _parseGenericWesternName: function (parts) { - /* Western names are parsed as follows, and rules are applied in this + /* Western names are parsed as follows, and rules are applied in this * order: * * G @@ -988,7 +988,7 @@ Name.prototype = { conjunctionIndex = this._findLastConjunction(parts); if (conjunctionIndex > 0) { - // if there's a conjunction that's not the first token, put everything up to and + // if there's a conjunction that's not the first token, put everything up to and // including the token after it into the first name, the last token into // the family name (if it exists) and everything else in to the middle name // 0 1 2 3 4 5 @@ -1025,200 +1025,200 @@ Name.prototype = { } } }, - + /** - * parse patrinomic name from the russian names + * parse patrinomic name from the russian names * @protected * @param {Array.} parts the current array of name parts * @return number index of the part which contains patronymic name */ _findPatronymicName: function(parts) { - var index, part; - for (index = 0; index < parts.length; index++) { - part = parts[index]; - if (typeof (part) === 'string') { - part = part.toLowerCase(); - - var subLength = this.info.patronymicName.length; - while(subLength--) { - if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) - return index; - } - } - } - return -1; + var index, part; + for (index = 0; index < parts.length; index++) { + part = parts[index]; + if (typeof (part) === 'string') { + part = part.toLowerCase(); + + var subLength = this.info.patronymicName.length; + while(subLength--) { + if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) + return index; + } + } + } + return -1; }, /** - * find if the given part is patronymic name - * - * @protected - * @param {string} part string from name parts @ - * @return number index of the part which contains familyName - */ + * find if the given part is patronymic name + * + * @protected + * @param {string} part string from name parts @ + * @return number index of the part which contains familyName + */ _isPatronymicName: function(part) { - var pName; - if ( typeof (part) === 'string') { - pName = part.toLowerCase(); - - var subLength = this.info.patronymicName.length; - while (subLength--) { - if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) - return true; - } - } - return false; + var pName; + if ( typeof (part) === 'string') { + pName = part.toLowerCase(); + + var subLength = this.info.patronymicName.length; + while (subLength--) { + if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) + return true; + } + } + return false; }, /** - * find family name from the russian name - * - * @protected - * @param {Array.} parts the current array of name parts - * @return boolean true if patronymic, false otherwise - */ + * find family name from the russian name + * + * @protected + * @param {Array.} parts the current array of name parts + * @return boolean true if patronymic, false otherwise + */ _findFamilyName: function(parts) { - var index, part, substring; - for (index = 0; index < parts.length; index++) { - part = parts[index]; - - if ( typeof (part) === 'string') { - part = part.toLowerCase(); - var length = part.length - 1; - - if (this.info.familyName.indexOf(part) !== -1) { - return index; - } else if (part[length] === 'в' || part[length] === 'н' || - part[length] === 'й') { - substring = part.slice(0, -1); - if (this.info.familyName.indexOf(substring) !== -1) { - return index; - } - } else if ((part[length - 1] === 'в' && part[length] === 'а') || - (part[length - 1] === 'н' && part[length] === 'а') || - (part[length - 1] === 'а' && part[length] === 'я')) { - substring = part.slice(0, -2); - if (this.info.familyName.indexOf(substring) !== -1) { - return index; - } - } - } - } - return -1; + var index, part, substring; + for (index = 0; index < parts.length; index++) { + part = parts[index]; + + if ( typeof (part) === 'string') { + part = part.toLowerCase(); + var length = part.length - 1; + + if (this.info.familyName.indexOf(part) !== -1) { + return index; + } else if (part[length] === 'в' || part[length] === 'н' || + part[length] === 'й') { + substring = part.slice(0, -1); + if (this.info.familyName.indexOf(substring) !== -1) { + return index; + } + } else if ((part[length - 1] === 'в' && part[length] === 'а') || + (part[length - 1] === 'н' && part[length] === 'а') || + (part[length - 1] === 'а' && part[length] === 'я')) { + substring = part.slice(0, -2); + if (this.info.familyName.indexOf(substring) !== -1) { + return index; + } + } + } + } + return -1; }, /** - * parse russian name - * - * @protected - * @param {Array.} parts the current array of name parts - * @return - */ + * parse russian name + * + * @protected + * @param {Array.} parts the current array of name parts + * @return + */ _parseRussianName: function(parts) { - var conjunctionIndex, familyIndex = -1; - - if (parts.length === 1) { - if (this.prefix || typeof (parts[0]) === 'object') { - // already has a prefix, so assume it goes with the family name - // like "Dr. Roberts" or - // it is a name with auxillaries, which is almost always a - // family name - this.familyName = parts[0]; - } else { - this.givenName = parts[0]; - } - } else if (parts.length === 2) { - // we do G F - if (this.info.order === 'fgm') { - this.givenName = parts[1]; - this.familyName = parts[0]; - } else if (this.info.order === "gmf") { - this.givenName = parts[0]; - this.familyName = parts[1]; - } else if ( typeof (this.info.order) === 'undefined') { - if (this._isPatronymicName(parts[1]) === true) { - this.middleName = parts[1]; - this.givenName = parts[0]; - } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { - if (familyIndex === 1) { - this.givenName = parts[0]; - this.familyName = parts[1]; - } else { - this.familyName = parts[0]; - this.givenName = parts[1]; - } - - } else { - this.givenName = parts[0]; - this.familyName = parts[1]; - } - - } - } else if (parts.length >= 3) { - // find the first instance of 'and' in the name - conjunctionIndex = this._findLastConjunction(parts); - var patronymicNameIndex = this._findPatronymicName(parts); - if (conjunctionIndex > 0) { - // if there's a conjunction that's not the first token, put - // everything up to and - // including the token after it into the first name, the last - // token into - // the family name (if it exists) and everything else in to the - // middle name - // 0 1 2 3 4 5 - // G A G M M F - // G G A G M F - // G G G A G F - // G G G G A G - // if(this.order == "gmf") { - this.givenName = parts.slice(0, conjunctionIndex + 2); - - if (conjunctionIndex + 1 < parts.length - 1) { - this.familyName = parts.splice(parts.length - 1, 1); - // //console.log(this.familyName); - if (conjunctionIndex + 2 < parts.length - 1) { - this.middleName = parts.slice(conjunctionIndex + 2, - parts.length - conjunctionIndex - 3); - } - } else if (this.order == "fgm") { - this.familyName = parts.slice(0, conjunctionIndex + 2); - if (conjunctionIndex + 1 < parts.length - 1) { - this.middleName = parts.splice(parts.length - 1, 1); - if (conjunctionIndex + 2 < parts.length - 1) { - this.givenName = parts.slice(conjunctionIndex + 2, - parts.length - conjunctionIndex - 3); - } - } - } - } else if (patronymicNameIndex !== -1) { - this.middleName = parts[patronymicNameIndex]; - - if (patronymicNameIndex === (parts.length - 1)) { - this.familyName = parts[0]; - this.givenName = parts.slice(1, patronymicNameIndex); - } else { - this.givenName = parts.slice(0, patronymicNameIndex); - - this.familyName = parts[parts.length - 1]; - } - } else { - this.givenName = parts[0]; - - this.middleName = parts.slice(1, parts.length - 1); - - this.familyName = parts[parts.length - 1]; - } - } + var conjunctionIndex, familyIndex = -1; + + if (parts.length === 1) { + if (this.prefix || typeof (parts[0]) === 'object') { + // already has a prefix, so assume it goes with the family name + // like "Dr. Roberts" or + // it is a name with auxillaries, which is almost always a + // family name + this.familyName = parts[0]; + } else { + this.givenName = parts[0]; + } + } else if (parts.length === 2) { + // we do G F + if (this.info.order === 'fgm') { + this.givenName = parts[1]; + this.familyName = parts[0]; + } else if (this.info.order === "gmf") { + this.givenName = parts[0]; + this.familyName = parts[1]; + } else if ( typeof (this.info.order) === 'undefined') { + if (this._isPatronymicName(parts[1]) === true) { + this.middleName = parts[1]; + this.givenName = parts[0]; + } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { + if (familyIndex === 1) { + this.givenName = parts[0]; + this.familyName = parts[1]; + } else { + this.familyName = parts[0]; + this.givenName = parts[1]; + } + + } else { + this.givenName = parts[0]; + this.familyName = parts[1]; + } + + } + } else if (parts.length >= 3) { + // find the first instance of 'and' in the name + conjunctionIndex = this._findLastConjunction(parts); + var patronymicNameIndex = this._findPatronymicName(parts); + if (conjunctionIndex > 0) { + // if there's a conjunction that's not the first token, put + // everything up to and + // including the token after it into the first name, the last + // token into + // the family name (if it exists) and everything else in to the + // middle name + // 0 1 2 3 4 5 + // G A G M M F + // G G A G M F + // G G G A G F + // G G G G A G + // if(this.order == "gmf") { + this.givenName = parts.slice(0, conjunctionIndex + 2); + + if (conjunctionIndex + 1 < parts.length - 1) { + this.familyName = parts.splice(parts.length - 1, 1); + // //console.log(this.familyName); + if (conjunctionIndex + 2 < parts.length - 1) { + this.middleName = parts.slice(conjunctionIndex + 2, + parts.length - conjunctionIndex - 3); + } + } else if (this.order == "fgm") { + this.familyName = parts.slice(0, conjunctionIndex + 2); + if (conjunctionIndex + 1 < parts.length - 1) { + this.middleName = parts.splice(parts.length - 1, 1); + if (conjunctionIndex + 2 < parts.length - 1) { + this.givenName = parts.slice(conjunctionIndex + 2, + parts.length - conjunctionIndex - 3); + } + } + } + } else if (patronymicNameIndex !== -1) { + this.middleName = parts[patronymicNameIndex]; + + if (patronymicNameIndex === (parts.length - 1)) { + this.familyName = parts[0]; + this.givenName = parts.slice(1, patronymicNameIndex); + } else { + this.givenName = parts.slice(0, patronymicNameIndex); + + this.familyName = parts[parts.length - 1]; + } + } else { + this.givenName = parts[0]; + + this.middleName = parts.slice(1, parts.length - 1); + + this.familyName = parts[parts.length - 1]; + } + } }, - - + + /** * @protected */ _parseWesternName: function (parts) { if (this.locale.getLanguage() === "es" || this.locale.getLanguage() === "pt") { - // in spain and mexico and portugal, we parse names differently than in the rest of the world + // in spain and mexico and portugal, we parse names differently than in the rest of the world // because of the double family names this._parseSpanishName(parts); } else if (this.locale.getLanguage() === "ru") { @@ -1229,11 +1229,11 @@ Name.prototype = { */ this._parseRussianName(parts); } else if (this.locale.getLanguage() === "id") { - // in indonesia, we parse names differently than in the rest of the world + // in indonesia, we parse names differently than in the rest of the world // because names don't have family names usually. this._parseIndonesianName(parts); } else { - this._parseGenericWesternName(parts); + this._parseGenericWesternName(parts); } }, @@ -1295,7 +1295,7 @@ Name.prototype = { getHeadFamilyName: function () {}, - /** + /** * @protected * Return a shallow copy of the current instance. */ diff --git a/js/lib/NameFmt.js b/js/lib/NameFmt.js index 6275e67386..556f3067cf 100644 --- a/js/lib/NameFmt.js +++ b/js/lib/NameFmt.js @@ -1,6 +1,6 @@ /* * NameFmt.js - Format person names for display - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data name -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); @@ -32,22 +32,22 @@ var isPunct = require("./isPunct.js"); * @class * Creates a formatter that can format person name instances (Name) for display to * a user. The options may contain the following properties: - * + * *

      - *
    • locale - Use the conventions of the given locale to construct the name format. + *
    • locale - Use the conventions of the given locale to construct the name format. *
    • style - Format the name with the given style. The value of this property - * should be one of the following strings: + * should be one of the following strings: *
        *
      • short - Format a short name with just the given and family names. eg. "John Smith" - *
      • medium - Format a medium-length name with the given, middle, and family names. + *
      • medium - Format a medium-length name with the given, middle, and family names. * eg. "John James Smith" *
      • long - Format a long name with all names available in the given name object, including * prefixes. eg. "Mr. John James Smith" *
      • full - Format a long name with all names available in the given name object, including * prefixes and suffixes. eg. "Mr. John James Smith, Jr." - *
      • formal_short - Format a name with the honorific or prefix/suffix and the family + *
      • formal_short - Format a name with the honorific or prefix/suffix and the family * name. eg. "Mr. Smith" - *
      • formal_long - Format a name with the honorific or prefix/suffix and the + *
      • formal_long - Format a name with the honorific or prefix/suffix and the * given and family name. eg. "Mr. John Smith" *
      *
    • components - Format the name with the given components in the correct @@ -62,44 +62,44 @@ var isPunct = require("./isPunct.js"); *
    • h - honorifics (selects the prefix or suffix as required by the locale) *
    *

    - * - * For example, the string "pf" would mean to only format any prefixes and family names + * + * For example, the string "pf" would mean to only format any prefixes and family names * together and leave out all the other parts of the name.

    - * - * The components can be listed in any order in the string. The components option + * + * The components can be listed in any order in the string. The components option * overrides the style option if both are specified. * - *

  • onLoad - a callback function to call when the locale info object is fully + *
  • onLoad - a callback function to call when the locale info object is fully * loaded. When the onLoad option is given, the localeinfo object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
  • sync - tell whether to load any missing locale data synchronously or + * + *
  • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
  • loadParams - an object containing parameters to pass to the + *
  • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. * - * - * Formatting names is a locale-dependent function, as the order of the components + * + * Formatting names is a locale-dependent function, as the order of the components * depends on the locale. The following explains some of the details:

    - * + * *

      - *
    • In Western countries, the given name comes first, followed by a space, followed + *
    • In Western countries, the given name comes first, followed by a space, followed * by the family name. In Asian countries, the family name comes first, followed immediately * by the given name with no space. But, that format is only used with Asian names written - * in ideographic characters. In Asian countries, especially ones where both an Asian and - * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to - * follow the language of the name. That is, Asian names are written in Asian style, and + * in ideographic characters. In Asian countries, especially ones where both an Asian and + * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to + * follow the language of the name. That is, Asian names are written in Asian style, and * Western names are written in Western style. This class follows that convention as - * well. + * well. *
    • In other Asian countries, Asian names * written in Latin script are written with Asian ordering. eg. "Xu Ping-an" instead * of the more Western order "Ping-an Xu", as the order is thought to go with the style @@ -107,290 +107,290 @@ var isPunct = require("./isPunct.js"); *
    • In some Spanish speaking countries, people often take both their maternal and * paternal last names as their own family name. When formatting a short or medium style * of that family name, only the paternal name is used. In the long style, all the names - * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and + * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and * the name "Ortiz" from his mother. His family name would be "Lopez Ortiz". The formatted * short style of his name would be simply "Juan Lopez" which only uses his paternal * family name of "Lopez". *
    • In many Western languages, it is common to use auxillary words in family names. For - * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not - * "Beethoven". This class ensures that the family name is formatted correctly with - * all auxillary words. + * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not + * "Beethoven". This class ensures that the family name is formatted correctly with + * all auxillary words. *
    - * - * + * + * * @constructor * @param {Object} options A set of options that govern how the formatter will behave */ var NameFmt = function(options) { - var sync = true; - - this.style = "short"; - this.loadParams = {}; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.style) { - this.style = options.style; - } - - if (options.components) { - this.components = options.components; - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - this.loadParams = options.loadParams; - } - } - - // set up defaults in case we need them - this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); - this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); - this.useFirstFamilyName = false; - - switch (this.style) { - default: - case "s": - case "short": - this.style = "short"; - break; - case "m": - case "medium": - this.style = "medium"; - break; - case "l": - case "long": - this.style = "long"; - break; - case "f": - case "full": - this.style = "full"; - break; - case "fs": - case "formal_short": - this.style = "formal_short"; - break; - case "fl": - case "formal_long": - this.style = "formal_long"; - break; - } - - this.locale = this.locale || new Locale(); - - isPunct._init(sync, this.loadParams, ilib.bind(this, function() { - Utils.loadData({ - object: "Name", - locale: this.locale, - name: "name.json", - sync: sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (info) { - this.info = info || Name.defaultInfo;; - this._init(); - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - })); + var sync = true; + + this.style = "short"; + this.loadParams = {}; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.style) { + this.style = options.style; + } + + if (options.components) { + this.components = options.components; + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + this.loadParams = options.loadParams; + } + } + + // set up defaults in case we need them + this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); + this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); + this.useFirstFamilyName = false; + + switch (this.style) { + default: + case "s": + case "short": + this.style = "short"; + break; + case "m": + case "medium": + this.style = "medium"; + break; + case "l": + case "long": + this.style = "long"; + break; + case "f": + case "full": + this.style = "full"; + break; + case "fs": + case "formal_short": + this.style = "formal_short"; + break; + case "fl": + case "formal_long": + this.style = "formal_long"; + break; + } + + this.locale = this.locale || new Locale(); + + isPunct._init(sync, this.loadParams, ilib.bind(this, function() { + Utils.loadData({ + object: "Name", + locale: this.locale, + name: "name.json", + sync: sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (info) { + this.info = info || Name.defaultInfo;; + this._init(); + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + })); }; NameFmt.prototype = { - /** - * @protected - */ - _init: function() { - var arr; - this.comps = {}; - - if (this.components) { - var valids = {"p":1,"g":1,"m":1,"f":1,"s":1,"h":1}; - arr = this.components.split(""); - this.comps = {}; - for (var i = 0; i < arr.length; i++) { - if (valids[arr[i].toLowerCase()]) { - this.comps[arr[i].toLowerCase()] = true; - } - } - } else { - var comps = this.info.components[this.style]; - if (typeof(comps) === "string") { - comps.split("").forEach(ilib.bind(this, function(c) { - this.comps[c] = true; - })); - } else { - this.comps = comps; - } - } - - this.template = new IString(this.info.format); - - if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { - this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal - } - - this.isAsianLocale = (this.info.nameStyle === "asian"); - }, - - /** - * adjoin auxillary words to their head words - * @protected - */ - _adjoinAuxillaries: function (parts, namePrefix) { - var start, i, prefixArray, prefix, prefixLower; - - //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); - - if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { - for ( start = 0; start < parts.length-1; start++ ) { - for ( i = parts.length; i > start; i-- ) { - prefixArray = parts.slice(start, i); - prefix = prefixArray.join(' '); - prefixLower = prefix.toLowerCase(); - prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods - - //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); - - if ( prefixLower in this.info.auxillaries ) { - //console.info("Found! Old parts list is " + JSON.stringify(parts)); - parts.splice(start, i+1-start, prefixArray.concat(parts[i])); - //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); - i = start; - } - } - } - } - - //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); - - return parts; - }, - - /** - * Return the locale for this formatter instance. - * @return {Locale} the locale instance for this formatter - */ - getLocale: function () { - return this.locale; - }, - - /** - * Return the style of names returned by this formatter - * @return {string} the style of names returned by this formatter - */ - getStyle: function () { - return this.style; - }, - - /** - * Return the list of components used to format names in this formatter - * @return {string} the list of components - */ - getComponents: function () { - return this.components; - }, - - /** - * Format the name for display in the current locale with the options set up - * in the constructor of this formatter instance.

    - * - * If the name does not contain all the parts required for the style, those parts - * will be left blank.

    - * - * There are two basic styles of formatting: European, and Asian. If this formatter object - * is set for European style, but an Asian name is passed to the format method, then this - * method will format the Asian name with a generic Asian template. Similarly, if the - * formatter is set for an Asian style, and a European name is passed to the format method, - * the formatter will use a generic European template.

    - * - * This means it is always safe to format any name with a formatter for any locale. You should - * always get something at least reasonable as output.

    - * - * @param {Name|Object} name the name instance to format, or an object containing name parts to format - * @return {string|undefined} the name formatted according to the style of this formatter instance - */ - format: function(name) { - var formatted, temp, modified, isAsianName; - var currentLanguage = this.locale.getLanguage(); - - if (!name || typeof(name) !== 'object') { - return undefined; - } - if (!(name instanceof Name)) { - // if the object is not a name, implicitly convert to a name so that the code below works - name = new Name(name, {locale: this.locale}); - } - - if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || - Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { - isAsianName = false; // this is a euro name, even if the locale is asian - modified = name.clone(); - - // handle the case where there is no space if there is punctuation in the suffix like ", Phd". - // Otherwise, put a space in to transform "PhD" to " PhD" - /* - console.log("suffix is " + modified.suffix); - if ( modified.suffix ) { - console.log("first char is " + modified.suffix.charAt(0)); - console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); - } - */ - if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { - modified.suffix = ' ' + modified.suffix; - } - - if (this.useFirstFamilyName && name.familyName) { - var familyNameParts = modified.familyName.trim().split(' '); - if (familyNameParts.length > 1) { - familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); - } //in spain and mexico, we parse names differently than in the rest of the world - - modified.familyName = familyNameParts[0]; - } - - modified._joinNameArrays(); - } else { - isAsianName = true; - modified = name; - } - - if (!this.template || isAsianName !== this.isAsianLocale) { - temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; - } else { - temp = this.template; - } - - // use the honorific as the prefix or the suffix as appropriate for the order of the name - if (modified.honorific) { - if ((this.order === 'fg' || isAsianName) && currentLanguage !== "ko") { - if (!modified.suffix) { - modified.suffix = modified.honorific - } - } else { - if (!modified.prefix) { - modified.prefix = modified.honorific - } - } - } - - var parts = { - prefix: this.comps["p"] && modified.prefix || "", - givenName: this.comps["g"] && modified.givenName || "", - middleName: this.comps["m"] && modified.middleName || "", - familyName: this.comps["f"] && modified.familyName || "", - suffix: this.comps["s"] && modified.suffix || "" - }; - - formatted = temp.format(parts); - return formatted.replace(/\s+/g, ' ').trim(); - } + /** + * @protected + */ + _init: function() { + var arr; + this.comps = {}; + + if (this.components) { + var valids = {"p":1,"g":1,"m":1,"f":1,"s":1,"h":1}; + arr = this.components.split(""); + this.comps = {}; + for (var i = 0; i < arr.length; i++) { + if (valids[arr[i].toLowerCase()]) { + this.comps[arr[i].toLowerCase()] = true; + } + } + } else { + var comps = this.info.components[this.style]; + if (typeof(comps) === "string") { + comps.split("").forEach(ilib.bind(this, function(c) { + this.comps[c] = true; + })); + } else { + this.comps = comps; + } + } + + this.template = new IString(this.info.format); + + if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { + this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal + } + + this.isAsianLocale = (this.info.nameStyle === "asian"); + }, + + /** + * adjoin auxillary words to their head words + * @protected + */ + _adjoinAuxillaries: function (parts, namePrefix) { + var start, i, prefixArray, prefix, prefixLower; + + //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); + + if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { + for ( start = 0; start < parts.length-1; start++ ) { + for ( i = parts.length; i > start; i-- ) { + prefixArray = parts.slice(start, i); + prefix = prefixArray.join(' '); + prefixLower = prefix.toLowerCase(); + prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods + + //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); + + if ( prefixLower in this.info.auxillaries ) { + //console.info("Found! Old parts list is " + JSON.stringify(parts)); + parts.splice(start, i+1-start, prefixArray.concat(parts[i])); + //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); + i = start; + } + } + } + } + + //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); + + return parts; + }, + + /** + * Return the locale for this formatter instance. + * @return {Locale} the locale instance for this formatter + */ + getLocale: function () { + return this.locale; + }, + + /** + * Return the style of names returned by this formatter + * @return {string} the style of names returned by this formatter + */ + getStyle: function () { + return this.style; + }, + + /** + * Return the list of components used to format names in this formatter + * @return {string} the list of components + */ + getComponents: function () { + return this.components; + }, + + /** + * Format the name for display in the current locale with the options set up + * in the constructor of this formatter instance.

    + * + * If the name does not contain all the parts required for the style, those parts + * will be left blank.

    + * + * There are two basic styles of formatting: European, and Asian. If this formatter object + * is set for European style, but an Asian name is passed to the format method, then this + * method will format the Asian name with a generic Asian template. Similarly, if the + * formatter is set for an Asian style, and a European name is passed to the format method, + * the formatter will use a generic European template.

    + * + * This means it is always safe to format any name with a formatter for any locale. You should + * always get something at least reasonable as output.

    + * + * @param {Name|Object} name the name instance to format, or an object containing name parts to format + * @return {string|undefined} the name formatted according to the style of this formatter instance + */ + format: function(name) { + var formatted, temp, modified, isAsianName; + var currentLanguage = this.locale.getLanguage(); + + if (!name || typeof(name) !== 'object') { + return undefined; + } + if (!(name instanceof Name)) { + // if the object is not a name, implicitly convert to a name so that the code below works + name = new Name(name, {locale: this.locale}); + } + + if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || + Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { + isAsianName = false; // this is a euro name, even if the locale is asian + modified = name.clone(); + + // handle the case where there is no space if there is punctuation in the suffix like ", Phd". + // Otherwise, put a space in to transform "PhD" to " PhD" + /* + console.log("suffix is " + modified.suffix); + if ( modified.suffix ) { + console.log("first char is " + modified.suffix.charAt(0)); + console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); + } + */ + if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { + modified.suffix = ' ' + modified.suffix; + } + + if (this.useFirstFamilyName && name.familyName) { + var familyNameParts = modified.familyName.trim().split(' '); + if (familyNameParts.length > 1) { + familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); + } //in spain and mexico, we parse names differently than in the rest of the world + + modified.familyName = familyNameParts[0]; + } + + modified._joinNameArrays(); + } else { + isAsianName = true; + modified = name; + } + + if (!this.template || isAsianName !== this.isAsianLocale) { + temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; + } else { + temp = this.template; + } + + // use the honorific as the prefix or the suffix as appropriate for the order of the name + if (modified.honorific) { + if ((this.order === 'fg' || isAsianName) && currentLanguage !== "ko") { + if (!modified.suffix) { + modified.suffix = modified.honorific + } + } else { + if (!modified.prefix) { + modified.prefix = modified.honorific + } + } + } + + var parts = { + prefix: this.comps["p"] && modified.prefix || "", + givenName: this.comps["g"] && modified.givenName || "", + middleName: this.comps["m"] && modified.middleName || "", + familyName: this.comps["f"] && modified.familyName || "", + suffix: this.comps["s"] && modified.suffix || "" + }; + + formatted = temp.format(parts); + return formatted.replace(/\s+/g, ' ').trim(); + } }; module.exports = NameFmt; diff --git a/js/lib/NodeLoader.js b/js/lib/NodeLoader.js index 37b941e83d..5b2b6392e1 100644 --- a/js/lib/NodeLoader.js +++ b/js/lib/NodeLoader.js @@ -1,6 +1,6 @@ /* * NodeLoader.js - Loader implementation for nodejs - * + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,80 +20,82 @@ /** * @class * Implementation of Loader for nodejs. - * + * * @constructor * @private */ module.exports = function (ilib) { - var path = require("./Path.js"), - fs = require("fs"), - util = require("util"), - Loader = require("./Loader.js"); - - var NodeLoader = function (ilib) { - // util.print("new common NodeLoader instance\n"); - - this.parent.call(this, ilib); - - // root of the app that created this loader - // this.root = root || process.cwd(); - this.root = process.cwd(); - - this.base = (typeof(module) !== 'undefined' && module.filename) ? - path.join(path.dirname(module.filename), "..") : - this.root; - - //console.log("module.filename is " + module.filename + "\n"); - //console.log("base is defined as " + this.base + "\n"); - - this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first - - // then a standard locale dir of a built version of ilib from npm - this._exists(path.join(this.base, "locale"), "localeinfo.json"); - - // try the standard install directories - this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); - - // ... else fall back to see if we're in a check-out dir of ilib - // this._exists(path.join(this.base, "data", "locale"), "localeinfo.json"); - - // console.log("NodeLoader: include path is now " + JSON.stringify(this.includePath)); - }; - - // make this a subclass of loader - NodeLoader.prototype = new Loader(); - NodeLoader.prototype.parent = Loader; - NodeLoader.prototype.constructor = NodeLoader; - - NodeLoader.prototype.name = "NodeLoader"; - NodeLoader.prototype._loadFile = function (pathname, sync, cb) { - var text; - //console.log("NodeLoader._loadFile: loading " + pathname + (sync ? " sync" : " async")); - try { - // on node, just secret load everything synchronously, even when asynchronous - // load is requested, or else you will get crazy results where files are not read - // until a long time later when the run queue is free - text = fs.readFileSync(pathname, "utf-8"); - if (typeof(cb) === 'function') { - cb(text); - } - } catch (e) { - //console.log("NodeLoader._loadFile: caught exception"); - if (typeof(cb) === 'function') { - cb(); - } - } - return text; - }; - - NodeLoader.prototype._exists = function(dir, file) { - var fullpath = path.normalize(path.join(dir, file)); - //console.log("NodeLoader._exists: checking for the existence of " + dir); - if (fs.existsSync(fullpath)) { - //console.log("NodeLoader._exists: found"); - this.includePath.push(dir); - } - }; + var path = require("./Path.js"), + fs = require("fs"), + util = require("util"), + Loader = require("./Loader.js"); + + var NodeLoader = function (ilib) { + // util.print("new common NodeLoader instance\n"); + + this.parent.call(this, ilib); + + // root of the app that created this loader + // this.root = root || process.cwd(); + this.root = process.cwd(); + + this.base = (typeof(module) !== 'undefined' && module.filename) ? + path.join(path.dirname(module.filename), "..") : + this.root; + + //console.log("module.filename is " + module.filename + "\n"); + //console.log("base is defined as " + this.base + "\n"); + + // this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first + this._exists(this.root, "resources"); // always check the application's resources dir first + this._exists(path.join(this.root, "locale"), "localeinfo.json"); + + // then a standard locale dir of a built version of ilib from npm + this._exists(path.join(this.base, "locale"), "localeinfo.json"); + + // try the standard install directories + this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); + + // ... else fall back to see if we're in a check-out dir of ilib + // this._exists(path.join(this.base, "data", "locale"), "localeinfo.json"); + + // console.log("NodeLoader: include path is now " + JSON.stringify(this.includePath)); + }; + + // make this a subclass of loader + NodeLoader.prototype = new Loader(); + NodeLoader.prototype.parent = Loader; + NodeLoader.prototype.constructor = NodeLoader; + + NodeLoader.prototype.name = "NodeLoader"; + NodeLoader.prototype._loadFile = function (pathname, sync, cb) { + var text; + //console.log("NodeLoader._loadFile: loading " + pathname + (sync ? " sync" : " async")); + try { + // on node, just secret load everything synchronously, even when asynchronous + // load is requested, or else you will get crazy results where files are not read + // until a long time later when the run queue is free + text = fs.readFileSync(pathname, "utf-8"); + if (typeof(cb) === 'function') { + cb(text); + } + } catch (e) { + //console.log("NodeLoader._loadFile: caught exception"); + if (typeof(cb) === 'function') { + cb(); + } + } + return text; + }; + + NodeLoader.prototype._exists = function(dir, file) { + var fullpath = path.normalize(path.join(dir, file)); + //console.log("NodeLoader._exists: checking for the existence of " + dir); + if (this.includePath.indexOf(fullpath) === -1 && fs.existsSync(fullpath)) { + //console.log("NodeLoader._exists: found"); + this.includePath.push(dir); + } + }; - return new NodeLoader(ilib); + return new NodeLoader(ilib); }; diff --git a/js/lib/NormString.js b/js/lib/NormString.js index 25f8da1456..9ff6b192e1 100644 --- a/js/lib/NormString.js +++ b/js/lib/NormString.js @@ -1,6 +1,6 @@ /* * NormString.js - ilib normalized string subclass definition - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data ccc nfd nfc nfkd nfkc -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -138,7 +138,7 @@ NormString.init = function(options) { loadParams: loadParams, callback: ilib.bind(this, function(normdata){ ilib.data.ccc = normdata; - + if (files.length) { //console.log("loading files " + JSON.stringify(files)); Utils._callLoadData(files, sync, loadParams, function(arr) { @@ -307,7 +307,7 @@ NormString._expand = function (ch, canon, compat) { * The normalization data is organized by normalization form and within there * by script. To include the normalization data for a particular script with * a particular normalization form, use the following require: - * + * *

    
      * NormString.init({
      *   form: "<form>",
    @@ -350,15 +350,15 @@ NormString._expand = function (ch, canon, compat) {
      * that are commonly used in many different scripts. Examples of characters in the
      * Common script are the ASCII punctuation characters, or the ASCII Arabic
      * numerals "0" through "9".

    - * - * By default, none of the data for normalization is automatically - * included in the preassembled ilib files. (For size "full".) + * + * By default, none of the data for normalization is automatically + * included in the preassembled ilib files. (For size "full".) * If you would like to normalize strings, you must assemble * your own copy of ilib and explicitly include the normalization data - * for those scripts. This normalization method will - * produce output, even without the normalization data. However, the output will be - * simply the same thing as its input for all scripts - * except Korean Hangul and Jamo, which are decomposed and recomposed + * for those scripts. This normalization method will + * produce output, even without the normalization data. However, the output will be + * simply the same thing as its input for all scripts + * except Korean Hangul and Jamo, which are decomposed and recomposed * algorithmically and therefore do not rely on data.

    * * If characters are encountered for which there are no normalization data, they @@ -462,7 +462,7 @@ NormString.prototype.normalize = function (form) { // found a non-starter... rearrange all the non-starters until the next starter end = i+1; while (end < cpArray.length && - typeof(ilib.data.ccc[cpArray[end]]) !== 'undefined' && + typeof(ilib.data.ccc[cpArray[end]]) !== 'undefined' && ccc(cpArray[end]) !== 0) { end++; } @@ -485,10 +485,10 @@ NormString.prototype.normalize = function (form) { end = i+1; var notdone = true; while (end < cpArray.length && notdone) { - if (typeof(ilib.data.ccc[cpArray[end]]) !== 'undefined' && + if (typeof(ilib.data.ccc[cpArray[end]]) !== 'undefined' && ilib.data.ccc[cpArray[end]] !== 0) { - if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { - // not blocked + if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { + // not blocked testChar = GlyphString._compose(cpArray[i], cpArray[end]); if (typeof(testChar) !== 'undefined') { cpArray[i] = testChar; diff --git a/js/lib/NumFmt.js b/js/lib/NumFmt.js index d91374bbd1..6759f43648 100644 --- a/js/lib/NumFmt.js +++ b/js/lib/NumFmt.js @@ -30,7 +30,7 @@ JSUtils.js // !data localeinfo currency -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var MathUtils = require("./MathUtils.js"); @@ -150,35 +150,35 @@ var IString = require("./IString.js"); * @param {Object.} options A set of options that govern how the formatter will behave */ var NumFmt = function (options) { - var sync = true; - this.locale = new Locale(); - /** - * @private - * @type {string} - */ - this.type = "number"; - var loadParams = undefined; - - if (options) { - if (options.locale) { - this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.type) { - if (options.type === 'number' || - options.type === 'currency' || - options.type === 'percentage') { - this.type = options.type; - } - } - - if (options.currency) { - /** - * @private - * @type {string} - */ - this.currency = options.currency; - } + var sync = true; + this.locale = new Locale(); + /** + * @private + * @type {string} + */ + this.type = "number"; + var loadParams = undefined; + + if (options) { + if (options.locale) { + this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.type) { + if (options.type === 'number' || + options.type === 'currency' || + options.type === 'percentage') { + this.type = options.type; + } + } + + if (options.currency) { + /** + * @private + * @type {string} + */ + this.currency = options.currency; + } if (typeof (options.maxFractionDigits) !== 'undefined') { /** @@ -215,102 +215,102 @@ var NumFmt = function (options) { this.significantDigits = 20; } } - if (options.style) { - /** - * @private - * @type {string} - */ - this.style = options.style; - } - if (typeof(options.useNative) === 'boolean') { - /** - * @private - * @type {boolean} - * */ - this.useNative = options.useNative; - } - /** - * @private - * @type {string} - */ - this.roundingMode = options.roundingMode; - - if (typeof(options.sync) === 'boolean') { - sync = options.sync; - } - - loadParams = options.loadParams; - } - - /** - * @private - * @type {LocaleInfo|undefined} - */ - this.localeInfo = undefined; - - new LocaleInfo(this.locale, { - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (li) { - /** - * @private - * @type {LocaleInfo|undefined} - */ - this.localeInfo = li; - - if (this.type === "number") { - this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); - } else if (this.type === "currency") { - var templates; - - if (!this.currency || typeof (this.currency) != 'string') { - throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; - } - - new Currency({ - locale: this.locale, - code: this.currency, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (cur) { - this.currencyInfo = cur; - if (this.style !== "common" && this.style !== "iso") { - this.style = "common"; - } - - if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { - this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); - } - - templates = this.localeInfo.getCurrencyFormats(); - this.template = new IString(templates[this.style] || templates.common); - this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); - this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); - - if (!this.roundingMode) { - this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; - } - - this._init(); - - if (options && typeof (options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - return; - } else if (this.type === "percentage") { - this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); - this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); - } - - this._init(); - - if (options && typeof (options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + if (options.style) { + /** + * @private + * @type {string} + */ + this.style = options.style; + } + if (typeof(options.useNative) === 'boolean') { + /** + * @private + * @type {boolean} + * */ + this.useNative = options.useNative; + } + /** + * @private + * @type {string} + */ + this.roundingMode = options.roundingMode; + + if (typeof(options.sync) === 'boolean') { + sync = options.sync; + } + + loadParams = options.loadParams; + } + + /** + * @private + * @type {LocaleInfo|undefined} + */ + this.localeInfo = undefined; + + new LocaleInfo(this.locale, { + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (li) { + /** + * @private + * @type {LocaleInfo|undefined} + */ + this.localeInfo = li; + + if (this.type === "number") { + this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); + } else if (this.type === "currency") { + var templates; + + if (!this.currency || typeof (this.currency) != 'string') { + throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; + } + + new Currency({ + locale: this.locale, + code: this.currency, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (cur) { + this.currencyInfo = cur; + if (this.style !== "common" && this.style !== "iso") { + this.style = "common"; + } + + if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { + this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); + } + + templates = this.localeInfo.getCurrencyFormats(); + this.template = new IString(templates[this.style] || templates.common); + this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); + this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); + + if (!this.roundingMode) { + this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; + } + + this._init(); + + if (options && typeof (options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + return; + } else if (this.type === "percentage") { + this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); + this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); + } + + this._init(); + + if (options && typeof (options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; /** @@ -319,7 +319,7 @@ var NumFmt = function (options) { * @return {Array.|undefined} an array of available locales */ NumFmt.getAvailableLocales = function () { - return undefined; + return undefined; }; /** @@ -330,63 +330,63 @@ NumFmt.getAvailableLocales = function () { NumFmt.zeros = "0000000000000000000000000000000000000000000000000000000000000000000000"; NumFmt.prototype = { - /** - * Return true if this formatter uses native digits to format the number. If the useNative - * option is given to the constructor, then this flag will be honoured. If the useNative - * option is not given to the constructor, this this formatter will use native digits if - * the locale typically uses native digits. - * - * @return {boolean} true if this formatter will format with native digits, false otherwise - */ - getUseNative: function() { - if (typeof(this.useNative) === "boolean") { - return this.useNative; - } - return (this.localeInfo.getDigitsStyle() === "native"); - }, - - /** - * @private - */ - _init: function () { - if (this.maxFractionDigits < this.minFractionDigits) { - this.minFractionDigits = this.maxFractionDigits; - } - - if (!this.roundingMode) { - this.roundingMode = this.localeInfo.getRoundingMode(); - } - - if (!this.roundingMode) { - this.roundingMode = "halfdown"; - } - - // set up the function, so we only have to figure it out once - // and not every time we do format() - this.round = MathUtils[this.roundingMode]; - if (!this.round) { - this.roundingMode = "halfdown"; - this.round = MathUtils[this.roundingMode]; - } - - if (this.style === "nogrouping") { - this.prigroupSize = this.secgroupSize = 0; - } else { - this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); - this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); - this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); - } - this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); - - if (this.getUseNative()) { - var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); - if (nd) { - this.digits = nd.split(""); - } - } - - this.exponentSymbol = this.localeInfo.getExponential() || "e"; - }, + /** + * Return true if this formatter uses native digits to format the number. If the useNative + * option is given to the constructor, then this flag will be honoured. If the useNative + * option is not given to the constructor, this this formatter will use native digits if + * the locale typically uses native digits. + * + * @return {boolean} true if this formatter will format with native digits, false otherwise + */ + getUseNative: function() { + if (typeof(this.useNative) === "boolean") { + return this.useNative; + } + return (this.localeInfo.getDigitsStyle() === "native"); + }, + + /** + * @private + */ + _init: function () { + if (this.maxFractionDigits < this.minFractionDigits) { + this.minFractionDigits = this.maxFractionDigits; + } + + if (!this.roundingMode) { + this.roundingMode = this.localeInfo.getRoundingMode(); + } + + if (!this.roundingMode) { + this.roundingMode = "halfdown"; + } + + // set up the function, so we only have to figure it out once + // and not every time we do format() + this.round = MathUtils[this.roundingMode]; + if (!this.round) { + this.roundingMode = "halfdown"; + this.round = MathUtils[this.roundingMode]; + } + + if (this.style === "nogrouping") { + this.prigroupSize = this.secgroupSize = 0; + } else { + this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); + this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); + this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); + } + this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); + + if (this.getUseNative()) { + var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); + if (nd) { + this.digits = nd.split(""); + } + } + + this.exponentSymbol = this.localeInfo.getExponential() || "e"; + }, /** * Apply the constraints used in the current formatter to the given number. This will @@ -418,24 +418,24 @@ NumFmt.prototype = { return result; }, - /** - * Format the number using scientific notation as a positive number. Negative - * formatting to be applied later. - * @private - * @param {number} num the number to format - * @return {string} the formatted number - */ - _formatScientific: function (num) { - var n = new Number(num); - var formatted; - - var str = n.toExponential(), - parts = str.split("e"), - significant = parts[0], - exponent = parts[1], - numparts, - integral, - fraction; + /** + * Format the number using scientific notation as a positive number. Negative + * formatting to be applied later. + * @private + * @param {number} num the number to format + * @return {string} the formatted number + */ + _formatScientific: function (num) { + var n = new Number(num); + var formatted; + + var str = n.toExponential(), + parts = str.split("e"), + significant = parts[0], + exponent = parts[1], + numparts, + integral, + fraction; if (this.maxFractionDigits > 0 || this.significantDigits > 0) { // if there is a max fraction digits setting, round the fraction to @@ -448,33 +448,33 @@ NumFmt.prototype = { } significant = MathUtils.significant(Number(significant), maxDigits, this.round); } - numparts = ("" + significant).split("."); - integral = numparts[0]; - fraction = numparts[1]; - - if (typeof(this.maxFractionDigits) !== 'undefined') { - fraction = fraction.substring(0, this.maxFractionDigits); - } - if (typeof(this.minFractionDigits) !== 'undefined') { - fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); - } - formatted = integral; - if (fraction.length) { - formatted += this.decimalSeparator + fraction; - } - formatted += this.exponentSymbol + exponent; - return formatted; - }, - - /** - * Formats the number as a positive number. Negative formatting to be applied later. - * @private - * @param {number} num the number to format - * @return {string} the formatted number - */ - _formatStandard: function (num) { - var i; - var k; + numparts = ("" + significant).split("."); + integral = numparts[0]; + fraction = numparts[1]; + + if (typeof(this.maxFractionDigits) !== 'undefined') { + fraction = fraction.substring(0, this.maxFractionDigits); + } + if (typeof(this.minFractionDigits) !== 'undefined') { + fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); + } + formatted = integral; + if (fraction.length) { + formatted += this.decimalSeparator + fraction; + } + formatted += this.exponentSymbol + exponent; + return formatted; + }, + + /** + * Formats the number as a positive number. Negative formatting to be applied later. + * @private + * @param {number} num the number to format + * @return {string} the formatted number + */ + _formatStandard: function (num) { + var i; + var k; var parts, integral, @@ -484,49 +484,49 @@ NumFmt.prototype = { num = Math.abs(this.constrain(num)); - parts = ("" + num).split("."); - integral = parts[0].toString(); - fraction = parts[1]; - - if (this.minFractionDigits > 0) { - fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); - } - - if (this.secgroupSize > 0) { - if (integral.length > this.prigroupSize) { - var size1 = this.prigroupSize; - var size2 = integral.length; - var size3 = size2 - size1; - integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); - var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); - k = num_sec.length; - while (k > this.secgroupSize) { - var secsize1 = this.secgroupSize; - var secsize2 = num_sec.length; - var secsize3 = secsize2 - secsize1; - integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); - num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); - k = num_sec.length; - } - } - - formatted = integral; - } else if (this.prigroupSize !== 0) { - cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); - - formatted = ""; - - for (i = 0; i < integral.length - 1; i++) { - formatted += integral.charAt(i); - if (cycle === 0) { - formatted += this.groupingSeparator; - } - cycle = MathUtils.mod(cycle - 1, this.prigroupSize); - } - formatted += integral.charAt(integral.length - 1); - } else { - formatted = integral; - } + parts = ("" + num).split("."); + integral = parts[0].toString(); + fraction = parts[1]; + + if (this.minFractionDigits > 0) { + fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); + } + + if (this.secgroupSize > 0) { + if (integral.length > this.prigroupSize) { + var size1 = this.prigroupSize; + var size2 = integral.length; + var size3 = size2 - size1; + integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); + var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); + k = num_sec.length; + while (k > this.secgroupSize) { + var secsize1 = this.secgroupSize; + var secsize2 = num_sec.length; + var secsize3 = secsize2 - secsize1; + integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); + num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); + k = num_sec.length; + } + } + + formatted = integral; + } else if (this.prigroupSize !== 0) { + cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); + + formatted = ""; + + for (i = 0; i < integral.length - 1; i++) { + formatted += integral.charAt(i); + if (cycle === 0) { + formatted += this.groupingSeparator; + } + cycle = MathUtils.mod(cycle - 1, this.prigroupSize); + } + formatted += integral.charAt(integral.length - 1); + } else { + formatted = integral; + } if (fraction && ((typeof(this.maxFractionDigits) === 'undefined' && typeof(this.significantDigits) === 'undefined') || @@ -535,101 +535,101 @@ NumFmt.prototype = { formatted += fraction; } - if (this.digits) { - formatted = JSUtils.mapString(formatted, this.digits); - } - - return formatted; - }, - - /** - * Format a number according to the settings of this number formatter instance. - * @param num {number|string|INumber|Number} a floating point number to format - * @return {string} a string containing the formatted number - */ - format: function (num) { - var formatted, n; - - if (typeof (num) === 'undefined') { - return ""; - } - - // convert to a real primitive number type - n = Number(num); - - if (this.type === "number") { - formatted = (this.style === "scientific") ? - this._formatScientific(n) : - this._formatStandard(n); - - if (num < 0) { - formatted = this.templateNegative.format({n: formatted}); - } - } else { - formatted = this._formatStandard(n); - var template = (n < 0) ? this.templateNegative : this.template; - formatted = template.format({ - n: formatted, - s: this.sign - }); - } - - return formatted; - }, - - /** - * Return the type of formatter. Valid values are "number", "currency", and - * "percentage". - * - * @return {string} the type of formatter - */ - getType: function () { - return this.type; - }, - - /** - * Return the locale for this formatter instance. - * @return {Locale} the locale instance for this formatter - */ - getLocale: function () { - return this.locale; - }, - - /** - * Returns true if this formatter groups together digits in the integral - * portion of a number, based on the options set up in the constructor. In - * most western European cultures, this means separating every 3 digits - * of the integral portion of a number with a particular character. - * - * @return {boolean} true if this formatter groups digits in the integral - * portion of the number - */ - isGroupingUsed: function () { - return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); - }, - - /** - * Returns the maximum fraction digits set up in the constructor. - * - * @return {number} the maximum number of fractional digits this - * formatter will format, or -1 for no maximum - */ - getMaxFractionDigits: function () { - return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; - }, - - /** - * Returns the minimum fraction digits set up in the constructor. If - * the formatter has the type "currency", then the minimum fraction - * digits is the amount of digits that is standard for the currency - * in question unless overridden in the options to the constructor. - * - * @return {number} the minimum number of fractional digits this - * formatter will format, or -1 for no minimum - */ - getMinFractionDigits: function () { - return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; - }, + if (this.digits) { + formatted = JSUtils.mapString(formatted, this.digits); + } + + return formatted; + }, + + /** + * Format a number according to the settings of this number formatter instance. + * @param num {number|string|INumber|Number} a floating point number to format + * @return {string} a string containing the formatted number + */ + format: function (num) { + var formatted, n; + + if (typeof (num) === 'undefined') { + return ""; + } + + // convert to a real primitive number type + n = Number(num); + + if (this.type === "number") { + formatted = (this.style === "scientific") ? + this._formatScientific(n) : + this._formatStandard(n); + + if (num < 0) { + formatted = this.templateNegative.format({n: formatted}); + } + } else { + formatted = this._formatStandard(n); + var template = (n < 0) ? this.templateNegative : this.template; + formatted = template.format({ + n: formatted, + s: this.sign + }); + } + + return formatted; + }, + + /** + * Return the type of formatter. Valid values are "number", "currency", and + * "percentage". + * + * @return {string} the type of formatter + */ + getType: function () { + return this.type; + }, + + /** + * Return the locale for this formatter instance. + * @return {Locale} the locale instance for this formatter + */ + getLocale: function () { + return this.locale; + }, + + /** + * Returns true if this formatter groups together digits in the integral + * portion of a number, based on the options set up in the constructor. In + * most western European cultures, this means separating every 3 digits + * of the integral portion of a number with a particular character. + * + * @return {boolean} true if this formatter groups digits in the integral + * portion of the number + */ + isGroupingUsed: function () { + return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); + }, + + /** + * Returns the maximum fraction digits set up in the constructor. + * + * @return {number} the maximum number of fractional digits this + * formatter will format, or -1 for no maximum + */ + getMaxFractionDigits: function () { + return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; + }, + + /** + * Returns the minimum fraction digits set up in the constructor. If + * the formatter has the type "currency", then the minimum fraction + * digits is the amount of digits that is standard for the currency + * in question unless overridden in the options to the constructor. + * + * @return {number} the minimum number of fractional digits this + * formatter will format, or -1 for no minimum + */ + getMinFractionDigits: function () { + return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; + }, /** * Returns the significant digits set up in the constructor. @@ -641,40 +641,40 @@ NumFmt.prototype = { return typeof (this.significantDigits) !== 'undefined' ? this.significantDigits : -1; }, - /** - * Returns the ISO 4217 code for the currency that this formatter formats. - * IF the typeof this formatter is not "currency", then this method will - * return undefined. - * - * @return {string} the ISO 4217 code for the currency that this formatter - * formats, or undefined if this not a currency formatter - */ - getCurrency: function () { - return this.currencyInfo && this.currencyInfo.getCode(); - }, - - /** - * Returns the rounding mode set up in the constructor. The rounding mode - * controls how numbers are rounded when the integral or fraction digits - * of a number are limited. - * - * @return {string} the name of the rounding mode used in this formatter - */ - getRoundingMode: function () { - return this.roundingMode; - }, - - /** - * If this formatter is a currency formatter, then the style determines how the - * currency is denoted in the formatted output. This method returns the style - * that this formatter will produce. (See the constructor comment for more about - * the styles.) - * @return {string} the name of the style this formatter will use to format - * currency amounts, or "undefined" if this formatter is not a currency formatter - */ - getStyle: function () { - return this.style; - } + /** + * Returns the ISO 4217 code for the currency that this formatter formats. + * IF the typeof this formatter is not "currency", then this method will + * return undefined. + * + * @return {string} the ISO 4217 code for the currency that this formatter + * formats, or undefined if this not a currency formatter + */ + getCurrency: function () { + return this.currencyInfo && this.currencyInfo.getCode(); + }, + + /** + * Returns the rounding mode set up in the constructor. The rounding mode + * controls how numbers are rounded when the integral or fraction digits + * of a number are limited. + * + * @return {string} the name of the rounding mode used in this formatter + */ + getRoundingMode: function () { + return this.roundingMode; + }, + + /** + * If this formatter is a currency formatter, then the style determines how the + * currency is denoted in the formatted output. This method returns the style + * that this formatter will produce. (See the constructor comment for more about + * the styles.) + * @return {string} the name of the style this formatter will use to format + * currency amounts, or "undefined" if this formatter is not a currency formatter + */ + getStyle: function () { + return this.style; + } }; module.exports = NumFmt; diff --git a/js/lib/NumberingPlan.js b/js/lib/NumberingPlan.js index 99e4602e39..05c82580ce 100644 --- a/js/lib/NumberingPlan.js +++ b/js/lib/NumberingPlan.js @@ -1,6 +1,6 @@ /* * NumPlan.js - Represent a phone numbering plan. - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,218 +19,218 @@ // !data numplan -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); /** * @class * Create a numbering plan information instance for a particular country's plan.

    - * + * * The options may contain any of the following properties: - * + * *

      *
    • locale - locale for which the numbering plan is sought. This locale * will be mapped to the actual numbering plan, which may be shared amongst a * number of countries. * - *
    • onLoad - a callback function to call when the date format object is fully + *
    • onLoad - a callback function to call when the date format object is fully * loaded. When the onLoad option is given, the DateFmt object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
    • loadParams - an object containing parameters to pass to the + * + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * + * * @private * @constructor * @param {Object} options options governing the way this plan is loaded */ var NumberingPlan = function (options) { - var sync = true, - loadParams = {}; - - this.locale = new Locale(); - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - Utils.loadData({ - name: "numplan.json", - object: "NumberingPlan", - locale: this.locale, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (npdata) { - if (!npdata) { - npdata = { - "region": "XX", - "skipTrunk": false, - "trunkCode": "0", - "iddCode": "00", - "dialingPlan": "closed", - "commonFormatChars": " ()-./", - "fieldLengths": { - "areaCode": 0, - "cic": 0, - "mobilePrefix": 0, - "serviceCode": 0 - } - }; - } - - /** - * @type {{ - * region:string, - * skipTrunk:boolean, - * trunkCode:string, - * iddCode:string, - * dialingPlan:string, - * commonFormatChars:string, - * fieldLengths:Object., - * contextFree:boolean, - * findExtensions:boolean, - * trunkRequired:boolean, - * extendedAreaCodes:boolean - * }} - */ - this.npdata = npdata; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + var sync = true, + loadParams = {}; + + this.locale = new Locale(); + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + Utils.loadData({ + name: "numplan.json", + object: "NumberingPlan", + locale: this.locale, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (npdata) { + if (!npdata) { + npdata = { + "region": "XX", + "skipTrunk": false, + "trunkCode": "0", + "iddCode": "00", + "dialingPlan": "closed", + "commonFormatChars": " ()-./", + "fieldLengths": { + "areaCode": 0, + "cic": 0, + "mobilePrefix": 0, + "serviceCode": 0 + } + }; + } + + /** + * @type {{ + * region:string, + * skipTrunk:boolean, + * trunkCode:string, + * iddCode:string, + * dialingPlan:string, + * commonFormatChars:string, + * fieldLengths:Object., + * contextFree:boolean, + * findExtensions:boolean, + * trunkRequired:boolean, + * extendedAreaCodes:boolean + * }} + */ + this.npdata = npdata; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; NumberingPlan.prototype = { - /** - * Return the name of this plan. This may be different than the - * name of the region because sometimes multiple countries share - * the same plan. - * @return {string} the name of the plan - */ - getName: function() { - return this.npdata.region; - }, - - /** - * Return the trunk code of the current plan as a string. - * @return {string|undefined} the trunk code of the plan or - * undefined if there is no trunk code in this plan - */ - getTrunkCode: function() { - return this.npdata.trunkCode; - }, - - /** - * Return the international direct dialing code of this plan. - * @return {string} the IDD code of this plan - */ - getIDDCode: function() { - return this.npdata.iddCode; - }, - - /** - * Return the plan style for this plan. The plan style may be - * one of: - * - *
      - *
    • "open" - area codes may be left off if the caller is - * dialing to another number within the same area code - *
    • "closed" - the area code must always be specified, even - * if calling another number within the same area code - *
    - * - * @return {string} the plan style, "open" or "closed" - */ - getPlanStyle: function() { - return this.npdata.dialingPlan; - }, - /** [Need Comment] - * Return a contextFree - * - * @return {boolean} - */ - getContextFree: function() { - return this.npdata.contextFree; - }, - /** [Need Comment] - * Return a findExtensions - * - * @return {boolean} - */ - getFindExtensions: function() { - return this.npdata.findExtensions; - }, - /** [Need Comment] - * Return a skipTrunk - * - * @return {boolean} - */ - getSkipTrunk: function() { - return this.npdata.skipTrunk; - }, - /** [Need Comment] - * Return a skipTrunk - * - * @return {boolean} - */ - getTrunkRequired: function() { - return this.npdata.trunkRequired; - }, - /** - * Return true if this plan uses extended area codes. - * @return {boolean} true if the plan uses extended area codes - */ - getExtendedAreaCode: function() { - return this.npdata.extendedAreaCodes; - }, - /** - * Return a string containing all of the common format characters - * used to format numbers. - * @return {string} the common format characters fused in this locale - */ - getCommonFormatChars: function() { - return this.npdata.commonFormatChars; - }, - - /** - * Return the length of the field with the given name. If the length - * is returned as 0, this means it is variable length. - * - * @param {string} field name of the field for which the length is - * being sought - * @return {number} if positive, this gives the length of the given - * field. If zero, the field is variable length. If negative, the - * field is not known. - */ - getFieldLength: function (field) { - var dataField = this.npdata.fieldLengths; - - return dataField[field]; - } + /** + * Return the name of this plan. This may be different than the + * name of the region because sometimes multiple countries share + * the same plan. + * @return {string} the name of the plan + */ + getName: function() { + return this.npdata.region; + }, + + /** + * Return the trunk code of the current plan as a string. + * @return {string|undefined} the trunk code of the plan or + * undefined if there is no trunk code in this plan + */ + getTrunkCode: function() { + return this.npdata.trunkCode; + }, + + /** + * Return the international direct dialing code of this plan. + * @return {string} the IDD code of this plan + */ + getIDDCode: function() { + return this.npdata.iddCode; + }, + + /** + * Return the plan style for this plan. The plan style may be + * one of: + * + *
      + *
    • "open" - area codes may be left off if the caller is + * dialing to another number within the same area code + *
    • "closed" - the area code must always be specified, even + * if calling another number within the same area code + *
    + * + * @return {string} the plan style, "open" or "closed" + */ + getPlanStyle: function() { + return this.npdata.dialingPlan; + }, + /** [Need Comment] + * Return a contextFree + * + * @return {boolean} + */ + getContextFree: function() { + return this.npdata.contextFree; + }, + /** [Need Comment] + * Return a findExtensions + * + * @return {boolean} + */ + getFindExtensions: function() { + return this.npdata.findExtensions; + }, + /** [Need Comment] + * Return a skipTrunk + * + * @return {boolean} + */ + getSkipTrunk: function() { + return this.npdata.skipTrunk; + }, + /** [Need Comment] + * Return a skipTrunk + * + * @return {boolean} + */ + getTrunkRequired: function() { + return this.npdata.trunkRequired; + }, + /** + * Return true if this plan uses extended area codes. + * @return {boolean} true if the plan uses extended area codes + */ + getExtendedAreaCode: function() { + return this.npdata.extendedAreaCodes; + }, + /** + * Return a string containing all of the common format characters + * used to format numbers. + * @return {string} the common format characters fused in this locale + */ + getCommonFormatChars: function() { + return this.npdata.commonFormatChars; + }, + + /** + * Return the length of the field with the given name. If the length + * is returned as 0, this means it is variable length. + * + * @param {string} field name of the field for which the length is + * being sought + * @return {number} if positive, this gives the length of the given + * field. If zero, the field is variable length. If negative, the + * field is not known. + */ + getFieldLength: function (field) { + var dataField = this.npdata.fieldLengths; + + return dataField[field]; + } }; module.exports = NumberingPlan; diff --git a/js/lib/PersAlgoRataDie.js b/js/lib/PersAlgoRataDie.js index ae8dcf1321..d76c2e3fa3 100644 --- a/js/lib/PersAlgoRataDie.js +++ b/js/lib/PersAlgoRataDie.js @@ -1,6 +1,6 @@ /* * PersAlsoRataDie.js - Represent an RD date in the Persian algorithmic calendar - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,56 +23,56 @@ var RataDie = require("./RataDie.js"); /** * @class - * Construct a new Persian RD date number object. The constructor parameters can + * Construct a new Persian RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
      - *
    • unixtime - sets the time of this instance according to the given + *
    • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian - * + * *
    • julianday - sets the time of this instance according to the given * Julian Day instance or the Julian Day given as a float - * + * *
    • year - any integer, including 0 - * + * *
    • month - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. - * + * *
    • day - 1 to 31 - * - *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + * + *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation - * + * *
    • minute - 0 to 59 - * + * *
    • second - 0 to 59 - * + * *
    • millisecond - 0 to 999 - * + * *
    • date - use the given intrinsic Javascript date to initialize this one. *
    * * If the constructor is called with another Persian date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.

    - * - * If the constructor is called with no arguments at all or if none of the - * properties listed above are present, then the RD is calculate based on + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above are present, then the RD is calculate based on * the current date at the time of instantiation.

    - * + * * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).

    - * - * + * + * * @private * @constructor * @extends RataDie * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date */ var PersAlgoRataDie = function(params) { - this.cal = params && params.cal || new PersianAlgoCal(); - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new PersianAlgoCal(); + this.rd = NaN; + RataDie.call(this, params); }; PersAlgoRataDie.prototype = new RataDie(); @@ -90,22 +90,22 @@ PersAlgoRataDie.prototype.epoch = 1948319.5; * @private * @const * @type Array. - * the cumulative lengths of each month, for a non-leap year + * the cumulative lengths of each month, for a non-leap year */ PersAlgoRataDie.cumMonthLengths = [ 0, // Farvardin - 31, // Ordibehesht - 62, // Khordad - 93, // Tir - 124, // Mordad - 155, // Shahrivar - 186, // Mehr - 216, // Aban - 246, // Azar - 276, // Dey - 306, // Bahman - 336, // Esfand - 365 + 31, // Ordibehesht + 62, // Khordad + 93, // Tir + 124, // Mordad + 155, // Shahrivar + 186, // Mehr + 216, // Aban + 246, // Azar + 276, // Dey + 306, // Bahman + 336, // Esfand + 365 ]; /** @@ -116,39 +116,39 @@ PersAlgoRataDie.cumMonthLengths = [ * @param {Object} date the date components to calculate the RD from */ PersAlgoRataDie.prototype._setDateComponents = function(date) { - var year = this.cal.equivalentCycleYear(date.year); - var y = date.year - (date.year >= 0 ? 474 : 473); - var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); - var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; - var rdtime = (date.hour * 3600000 + - date.minute * 60000 + - date.second * 1000 + - date.millisecond) / - 86400000; - - /* - // console.log("getRataDie: converting " + JSON.stringify(this)); - console.log("getRataDie: year is " + year); - console.log("getRataDie: rd of years is " + rdOfYears); - console.log("getRataDie: day in year is " + dayInYear); - console.log("getRataDie: rdtime is " + rdtime); - console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); - */ - - this.rd = rdOfYears + dayInYear + rdtime; + var year = this.cal.equivalentCycleYear(date.year); + var y = date.year - (date.year >= 0 ? 474 : 473); + var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); + var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; + var rdtime = (date.hour * 3600000 + + date.minute * 60000 + + date.second * 1000 + + date.millisecond) / + 86400000; + + /* + // console.log("getRataDie: converting " + JSON.stringify(this)); + console.log("getRataDie: year is " + year); + console.log("getRataDie: rd of years is " + rdOfYears); + console.log("getRataDie: day in year is " + dayInYear); + console.log("getRataDie: rdtime is " + rdtime); + console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); + */ + + this.rd = rdOfYears + dayInYear + rdtime; }; /** - * Return the rd number of the particular day of the week on or before the + * Return the rd number of the particular day of the week on or before the * given rd. eg. The Sunday on or before the given rd. * @private * @param {number} rd the rata die date of the reference date - * @param {number} dayOfWeek the day of the week that is being sought relative + * @param {number} dayOfWeek the day of the week that is being sought relative * to the current date * @return {number} the rd of the day of the week */ PersAlgoRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); }; module.exports = PersAlgoRataDie; \ No newline at end of file diff --git a/js/lib/PersRataDie.js b/js/lib/PersRataDie.js index 4fa2ce6c67..3d37f8560a 100644 --- a/js/lib/PersRataDie.js +++ b/js/lib/PersRataDie.js @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var MathUtils = require("./MathUtils.js"); var Astro = require("./Astro.js"); @@ -73,17 +73,17 @@ var GregorianDate = require("./GregorianDate.js"); * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date */ var PersRataDie = function(params) { - this.rd = NaN; - Astro.initAstro( - params && typeof(params.sync) === 'boolean' ? params.sync : true, - params && params.loadParams, - ilib.bind(this, function (x) { - RataDie.call(this, params); - if (params && typeof(params.callback) === 'function') { - params.callback(this); - } - }) - ); + this.rd = NaN; + Astro.initAstro( + params && typeof(params.sync) === 'boolean' ? params.sync : true, + params && params.loadParams, + ilib.bind(this, function (x) { + RataDie.call(this, params); + if (params && typeof(params.callback) === 'function') { + params.callback(this); + } + }) + ); }; PersRataDie.prototype = new RataDie(); @@ -133,33 +133,33 @@ PersRataDie.prototype._tehranEquinox = function(year) { * @return {{year:number,equinox:number}} the year and the last equinox */ PersRataDie.prototype._getYear = function(jd) { - var gd = new GregorianDate({julianday: jd}); + var gd = new GregorianDate({julianday: jd}); var guess = gd.getYears() - 2, - nexteq, - ret = {}; + nexteq, + ret = {}; //ret.equinox = Math.floor(this._tehranEquinox(guess)); ret.equinox = this._tehranEquinox(guess); - while (ret.equinox > jd) { - guess--; - // ret.equinox = Math.floor(this._tehranEquinox(guess)); - ret.equinox = this._tehranEquinox(guess); - } - nexteq = ret.equinox - 1; - // if the equinox falls after noon, then the day after that is the start of the - // next year, so truncate the JD to get the noon of the day before the day with - //the equinox on it, then add 0.5 to get the midnight of that day - while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { - ret.equinox = nexteq; - guess++; - // nexteq = Math.floor(this._tehranEquinox(guess)); - nexteq = this._tehranEquinox(guess); - } - - // Mean solar tropical year is 365.24219878 days - ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; - - return ret; + while (ret.equinox > jd) { + guess--; + // ret.equinox = Math.floor(this._tehranEquinox(guess)); + ret.equinox = this._tehranEquinox(guess); + } + nexteq = ret.equinox - 1; + // if the equinox falls after noon, then the day after that is the start of the + // next year, so truncate the JD to get the noon of the day before the day with + //the equinox on it, then add 0.5 to get the midnight of that day + while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { + ret.equinox = nexteq; + guess++; + // nexteq = Math.floor(this._tehranEquinox(guess)); + nexteq = this._tehranEquinox(guess); + } + + // Mean solar tropical year is 365.24219878 days + ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; + + return ret; }; /** @@ -186,13 +186,13 @@ PersRataDie.prototype._setDateComponents = function(date) { (((date.month || 1) - 1) * 31) : ((((date.month || 1) - 1) * 30) + 6) ) + - ((date.day || 1) - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight + ((date.day || 1) - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight - jd += ((date.hour || 0) * 3600000 + - (date.minute || 0) * 60000 + - (date.second || 0) * 1000 + - (date.millisecond || 0)) / - 86400000; + jd += ((date.hour || 0) * 3600000 + + (date.minute || 0) * 60000 + + (date.second || 0) * 1000 + + (date.millisecond || 0)) / + 86400000; this.rd = jd - this.epoch; }; @@ -207,7 +207,7 @@ PersRataDie.prototype._setDateComponents = function(date) { * @return {number} the rd of the day of the week */ PersRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); }; module.exports = PersRataDie; \ No newline at end of file diff --git a/js/lib/PersianAlgoCal.js b/js/lib/PersianAlgoCal.js index 5c7c6c8afd..2a9d42d5b1 100644 --- a/js/lib/PersianAlgoCal.js +++ b/js/lib/PersianAlgoCal.js @@ -1,6 +1,6 @@ /* * PersianAlgoCal.js - Represent a Persian algorithmic calendar object. - * + * * Copyright © 2014-2015,2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,14 +24,14 @@ var Calendar = require("./Calendar.js"); * @class * Construct a new Persian algorithmic calendar object. This class encodes information about * a Persian algorithmic calendar.

    - * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var PersianAlgoCal = function(options) { - this.type = "persian-algo"; - + this.type = "persian-algo"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -40,67 +40,67 @@ var PersianAlgoCal = function(options) { /** * @private * @const - * @type Array. - * the lengths of each month + * @type Array. + * the lengths of each month */ PersianAlgoCal.monthLengths = [ - 31, // Farvardin - 31, // Ordibehesht - 31, // Khordad - 31, // Tir - 31, // Mordad - 31, // Shahrivar - 30, // Mehr - 30, // Aban - 30, // Azar - 30, // Dey - 30, // Bahman - 29 // Esfand + 31, // Farvardin + 31, // Ordibehesht + 31, // Khordad + 31, // Tir + 31, // Mordad + 31, // Shahrivar + 30, // Mehr + 30, // Aban + 30, // Azar + 30, // Dey + 30, // Bahman + 29 // Esfand ]; /** * Return the number of months in the given year. The number of months in a year varies - * for some luni-solar calendars because in some years, an extra month is needed to extend the + * for some luni-solar calendars because in some years, an extra month is needed to extend the * days in a year to an entire solar year. The month is represented as a 1-based number * where 1=first month, 2=second month, etc. - * + * * @param {number} year a year for which the number of months is sought * @return {number} The number of months in the given year */ PersianAlgoCal.prototype.getNumMonths = function(year) { - return 12; + return 12; }; /** * Return the number of days in a particular month in a particular year. This function * can return a different number for a month depending on the year because of things * like leap years. - * + * * @param {number} month the month for which the length is sought * @param {number} year the year within which that month can be found * @return {number} the number of days within the given month in the given year */ PersianAlgoCal.prototype.getMonLength = function(month, year) { - if (month !== 12 || !this.isLeapYear(year)) { - return PersianAlgoCal.monthLengths[month-1]; - } else { - // Month 12, Esfand, has 30 days instead of 29 in leap years - return 30; - } + if (month !== 12 || !this.isLeapYear(year)) { + return PersianAlgoCal.monthLengths[month-1]; + } else { + // Month 12, Esfand, has 30 days instead of 29 in leap years + return 30; + } }; /** - * Return the equivalent year in the 2820 year cycle that begins on - * Far 1, 474. This particular cycle obeys the cycle-of-years formula + * Return the equivalent year in the 2820 year cycle that begins on + * Far 1, 474. This particular cycle obeys the cycle-of-years formula * whereas the others do not specifically. This cycle can be used as - * a proxy for other years outside of the cycle by shifting them into - * the cycle. + * a proxy for other years outside of the cycle by shifting them into + * the cycle. * @param {number} year year to find the equivalent cycle year for * @returns {number} the equivalent cycle year */ PersianAlgoCal.prototype.equivalentCycleYear = function(year) { - var y = year - (year >= 0 ? 474 : 473); - return MathUtils.mod(y, 2820) + 474; + var y = year - (year >= 0 ? 474 : 473); + return MathUtils.mod(y, 2820) + 474; }; /** @@ -110,16 +110,16 @@ PersianAlgoCal.prototype.equivalentCycleYear = function(year) { * @return {boolean} true if the given year is a leap year */ PersianAlgoCal.prototype.isLeapYear = function(year) { - return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); + return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); }; /** * Return the type of this calendar. - * - * @return {string} the name of the type of this calendar + * + * @return {string} the name of the type of this calendar */ PersianAlgoCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/PersianAlgoDate.js b/js/lib/PersianAlgoDate.js index 4f354d42c6..5f951c43ec 100644 --- a/js/lib/PersianAlgoDate.js +++ b/js/lib/PersianAlgoDate.js @@ -1,6 +1,6 @@ /* * PersianAlgoDate.js - Represent a date in the Persian algorithmic calendar - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var SearchUtils = require("./SearchUtils.js"); var MathUtils = require("./MathUtils.js"); @@ -31,81 +31,81 @@ var PersAlgoRataDie = require("./PersAlgoRataDie.js"); /** * @class - * - * Construct a new Persian date object. The constructor parameters can + * + * Construct a new Persian date object. The constructor parameters can * contain any of the following properties: - * + * *

      - *
    • unixtime - sets the time of this instance according to the given + *
    • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian - * + * *
    • julianday - sets the time of this instance according to the given * Julian Day instance or the Julian Day given as a float - * + * *
    • year - any integer, including 0 - * + * *
    • month - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. - * + * *
    • day - 1 to 31 - * - *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + * + *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation - * + * *
    • minute - 0 to 59 - * + * *
    • second - 0 to 59 - * + * *
    • millisecond - 0 to 999 - * - *
    • timezone - the TimeZone instance or time zone name as a string + * + *
    • timezone - the TimeZone instance or time zone name as a string * of this persian date. The date/time is kept in the local time. The time zone * is used later if this date is formatted according to a different time zone and * the difference has to be calculated, or when the date format has a time zone * component in it. - * - *
    • locale - locale for this persian date. If the time zone is not + * + *
    • locale - locale for this persian date. If the time zone is not * given, it can be inferred from this locale. For locales that span multiple - * time zones, the one with the largest population is chosen as the one that + * time zones, the one with the largest population is chosen as the one that * represents the locale. - * + * *
    • date - use the given intrinsic Javascript date to initialize this one. *
    * * If the constructor is called with another Persian date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.

    - * - * If the constructor is called with no arguments at all or if none of the - * properties listed above - * from unixtime through millisecond are present, then the date - * components are + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above + * from unixtime through millisecond are present, then the date + * components are * filled in with the current date at the time of instantiation. Note that if - * you do not give the time zone when defaulting to the current time and the + * you do not give the time zone when defaulting to the current time and the * time zone for all of ilib was not set with ilib.setTimeZone(), then the - * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich + * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich * Mean Time").

    - * + * * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).

    - * - * + * + * * @constructor * @extends IDate * @param {Object=} params parameters that govern the settings and behaviour of this Persian date */ var PersianAlgoDate = function(params) { - this.cal = new PersianAlgoCal(); - - params = params || {}; - - if (params.timezone) { - this.timezone = params.timezone; - } - if (params.locale) { - this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; - } - + this.cal = new PersianAlgoCal(); + + params = params || {}; + + if (params.timezone) { + this.timezone = params.timezone; + } + if (params.locale) { + this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; + } + if (!this.timezone) { if (this.locale) { new LocaleInfo(this.locale, { @@ -126,10 +126,10 @@ var PersianAlgoDate = function(params) { } - if (!this.rd) { - this.rd = this.newRd(params); - this._calcDateComponents(); - } + if (!this.rd) { + this.rd = this.newRd(params); + this._calcDateComponents(); + } }; PersianAlgoDate.prototype = new IDate({noinstance: true}); @@ -205,8 +205,8 @@ PersianAlgoDate.prototype._init = function (params) { onLoad: ilib.bind(this, function(tz) { this.tz = tz; // add the time zone offset to the rd to convert to UTC - // getOffsetMillis requires that this.year, this.rd, and this.dst - // are set in order to figure out which time zone rules apply and + // getOffsetMillis requires that this.year, this.rd, and this.dst + // are set in order to figure out which time zone rules apply and // what the offset is at that point in the year this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; if (this.offset !== 0) { @@ -244,22 +244,22 @@ PersianAlgoDate.prototype._init2 = function (params) { * @returns {RataDie} the new RD instance for the given params */ PersianAlgoDate.prototype.newRd = function (params) { - return new PersAlgoRataDie(params); + return new PersAlgoRataDie(params); }; /** * Return the year for the given RD * @protected - * @param {number} rd RD to calculate from + * @param {number} rd RD to calculate from * @returns {number} the year for the RD */ PersianAlgoDate.prototype._calcYear = function(rd) { - var shiftedRd = rd - 173126; - var numberOfCycles = Math.floor(shiftedRd / 1029983); - var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); - var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); - var year = 474 + 2820 * numberOfCycles + yearInCycle; - return (year > 0) ? year : year - 1; + var shiftedRd = rd - 173126; + var numberOfCycles = Math.floor(shiftedRd / 1029983); + var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); + var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); + var year = 474 + 2820 * numberOfCycles + yearInCycle; + return (year > 0) ? year : year - 1; }; /** @@ -267,108 +267,108 @@ PersianAlgoDate.prototype._calcYear = function(rd) { * Calculate date components for the given RD date. */ PersianAlgoDate.prototype._calcDateComponents = function () { - var remainder, - rd = this.rd.getRataDie(); - - this.year = this._calcYear(rd); - - if (typeof(this.offset) === "undefined") { - // now offset the RD by the time zone, then recalculate in case we were - // near the year boundary - if (!this.tz) { - this.tz = new TimeZone({id: this.timezone}); - } - this.offset = this.tz.getOffsetMillis(this) / 86400000; - } - - if (this.offset !== 0) { - rd += this.offset; - this.year = this._calcYear(rd); - } - - //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); - //console.log("PersAlgoDate.calcComponent: year is " + ret.year); - var yearStart = this.newRd({ - year: this.year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - remainder = rd - yearStart.getRataDie() + 1; - - this.dayOfYear = remainder; - - //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); - - this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); - remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; - - //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); - - this.day = Math.floor(remainder); - remainder -= this.day; - - //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); - - // now convert to milliseconds for the rest of the calculation - remainder = Math.round(remainder * 86400000); - - this.hour = Math.floor(remainder/3600000); - remainder -= this.hour * 3600000; - - this.minute = Math.floor(remainder/60000); - remainder -= this.minute * 60000; - - this.second = Math.floor(remainder/1000); - remainder -= this.second * 1000; - - this.millisecond = remainder; + var remainder, + rd = this.rd.getRataDie(); + + this.year = this._calcYear(rd); + + if (typeof(this.offset) === "undefined") { + // now offset the RD by the time zone, then recalculate in case we were + // near the year boundary + if (!this.tz) { + this.tz = new TimeZone({id: this.timezone}); + } + this.offset = this.tz.getOffsetMillis(this) / 86400000; + } + + if (this.offset !== 0) { + rd += this.offset; + this.year = this._calcYear(rd); + } + + //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); + //console.log("PersAlgoDate.calcComponent: year is " + ret.year); + var yearStart = this.newRd({ + year: this.year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + remainder = rd - yearStart.getRataDie() + 1; + + this.dayOfYear = remainder; + + //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); + + this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); + remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; + + //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); + + this.day = Math.floor(remainder); + remainder -= this.day; + + //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); + + // now convert to milliseconds for the rest of the calculation + remainder = Math.round(remainder * 86400000); + + this.hour = Math.floor(remainder/3600000); + remainder -= this.hour * 3600000; + + this.minute = Math.floor(remainder/60000); + remainder -= this.minute * 60000; + + this.second = Math.floor(remainder/1000); + remainder -= this.second * 1000; + + this.millisecond = remainder; }; /** * Return the day of the week of this date. The day of the week is encoded * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. - * + * * @return {number} the day of the week */ PersianAlgoDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.getRataDie()); - return MathUtils.mod(rd-3, 7); + var rd = Math.floor(this.getRataDie()); + return MathUtils.mod(rd-3, 7); }; /** - * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to - * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and + * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to + * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and * December 31st is 365 in regular years, or 366 in leap years. * @return {number} the ordinal day of the year */ PersianAlgoDate.prototype.getDayOfYear = function() { - return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; + return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; }; /** - * Return the era for this date as a number. The value for the era for Persian - * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno - * persico or AP). - * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, + * Return the era for this date as a number. The value for the era for Persian + * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno + * persico or AP). + * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, * there is a year 0, so any years that are negative or zero are BP. - * @return {number} 1 if this date is in the common era, -1 if it is before the - * common era + * @return {number} 1 if this date is in the common era, -1 if it is before the + * common era */ PersianAlgoDate.prototype.getEra = function() { - return (this.year < 1) ? -1 : 1; + return (this.year < 1) ? -1 : 1; }; /** * Return the name of the calendar that governs this date. - * + * * @return {string} a string giving the name of the calendar */ PersianAlgoDate.prototype.getCalendar = function() { - return "persian-algo"; + return "persian-algo"; }; // register with the factory method diff --git a/js/lib/PersianCal.js b/js/lib/PersianCal.js index 6d98ad24f6..830f908a14 100644 --- a/js/lib/PersianCal.js +++ b/js/lib/PersianCal.js @@ -1,6 +1,6 @@ /* * PersianCal.js - Represent a Persian astronomical (Hijjri) calendar object. - * + * * Copyright © 2014-2015,2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,19 +24,19 @@ var PersRataDie = require("./PersRataDie.js"); /** * @class - * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes - * information about a Persian calendar. This class differs from the + * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes + * information about a Persian calendar. This class differs from the * Persian calendar in that the leap years are calculated based on the * astronomical observations of the sun in Teheran, instead of calculating * the leap years based on a regular cyclical rhythm algorithm.

    - * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var PersianCal = function(options) { - this.type = "persian"; - + this.type = "persian"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -45,53 +45,53 @@ var PersianCal = function(options) { /** * @private * @const - * @type Array. - * the lengths of each month + * @type Array. + * the lengths of each month */ PersianCal.monthLengths = [ - 31, // Farvardin - 31, // Ordibehesht - 31, // Khordad - 31, // Tir - 31, // Mordad - 31, // Shahrivar - 30, // Mehr - 30, // Aban - 30, // Azar - 30, // Dey - 30, // Bahman - 29 // Esfand + 31, // Farvardin + 31, // Ordibehesht + 31, // Khordad + 31, // Tir + 31, // Mordad + 31, // Shahrivar + 30, // Mehr + 30, // Aban + 30, // Azar + 30, // Dey + 30, // Bahman + 29 // Esfand ]; /** * Return the number of months in the given year. The number of months in a year varies - * for some luni-solar calendars because in some years, an extra month is needed to extend the + * for some luni-solar calendars because in some years, an extra month is needed to extend the * days in a year to an entire solar year. The month is represented as a 1-based number * where 1=first month, 2=second month, etc. - * + * * @param {number} year a year for which the number of months is sought * @return {number} The number of months in the given year */ PersianCal.prototype.getNumMonths = function(year) { - return 12; + return 12; }; /** * Return the number of days in a particular month in a particular year. This function * can return a different number for a month depending on the year because of things * like leap years. - * + * * @param {number} month the month for which the length is sought * @param {number} year the year within which that month can be found * @return {number} the number of days within the given month in the given year */ PersianCal.prototype.getMonLength = function(month, year) { - if (month !== 12 || !this.isLeapYear(year)) { - return PersianCal.monthLengths[month-1]; - } else { - // Month 12, Esfand, has 30 days instead of 29 in leap years - return 30; - } + if (month !== 12 || !this.isLeapYear(year)) { + return PersianCal.monthLengths[month-1]; + } else { + // Month 12, Esfand, has 30 days instead of 29 in leap years + return 30; + } }; /** @@ -100,36 +100,36 @@ PersianCal.prototype.getMonLength = function(month, year) { * @return {boolean} true if the given year is a leap year */ PersianCal.prototype.isLeapYear = function(year) { - var rdNextYear = new PersRataDie({ - cal: this, - year: year + 1, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - var rdThisYear = new PersRataDie({ - cal: this, - year: year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); + var rdNextYear = new PersRataDie({ + cal: this, + year: year + 1, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + var rdThisYear = new PersRataDie({ + cal: this, + year: year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); return (rdNextYear.getRataDie() - rdThisYear.getRataDie()) > 365; }; /** * Return the type of this calendar. - * - * @return {string} the name of the type of this calendar + * + * @return {string} the name of the type of this calendar */ PersianCal.prototype.getType = function() { - return this.type; + return this.type; }; /* register this calendar for the factory method */ diff --git a/js/lib/PersianDate.js b/js/lib/PersianDate.js index e179c55796..503cb78f5f 100644 --- a/js/lib/PersianDate.js +++ b/js/lib/PersianDate.js @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var SearchUtils = require("./SearchUtils.js"); var MathUtils = require("./MathUtils.js"); var JSUtils = require("./JSUtils.js"); @@ -259,18 +259,18 @@ PersianDate.prototype._init2 = function (params) { */ PersianDate.cumMonthLengths = [ 0, // Farvardin - 31, // Ordibehesht - 62, // Khordad - 93, // Tir - 124, // Mordad - 155, // Shahrivar - 186, // Mehr - 216, // Aban - 246, // Azar - 276, // Dey - 306, // Bahman - 336, // Esfand - 366 + 31, // Ordibehesht + 62, // Khordad + 93, // Tir + 124, // Mordad + 155, // Shahrivar + 186, // Mehr + 216, // Aban + 246, // Azar + 276, // Dey + 306, // Bahman + 336, // Esfand + 366 ]; /** @@ -280,7 +280,7 @@ PersianDate.cumMonthLengths = [ * @returns {RataDie} the new RD instance for the given params */ PersianDate.prototype.newRd = function (params) { - return new PersRataDie(params); + return new PersRataDie(params); }; /** @@ -290,8 +290,8 @@ PersianDate.prototype.newRd = function (params) { * @returns {number} the year for the RD */ PersianDate.prototype._calcYear = function(rd) { - var julianday = rd + this.rd.epoch; - return this.rd._getYear(julianday).year; + var julianday = rd + this.rd.epoch; + return this.rd._getYear(julianday).year; }; /** @@ -299,65 +299,65 @@ PersianDate.prototype._calcYear = function(rd) { * Calculate date components for the given RD date. */ PersianDate.prototype._calcDateComponents = function () { - var remainder, - rd = this.rd.getRataDie(); + var remainder, + rd = this.rd.getRataDie(); - this.year = this._calcYear(rd); + this.year = this._calcYear(rd); - if (typeof(this.offset) === "undefined") { - // now offset the RD by the time zone, then recalculate in case we were - // near the year boundary - if (!this.tz) { - this.tz = new TimeZone({id: this.timezone}); - } - this.offset = this.tz.getOffsetMillis(this) / 86400000; - } + if (typeof(this.offset) === "undefined") { + // now offset the RD by the time zone, then recalculate in case we were + // near the year boundary + if (!this.tz) { + this.tz = new TimeZone({id: this.timezone}); + } + this.offset = this.tz.getOffsetMillis(this) / 86400000; + } - if (this.offset !== 0) { - rd += this.offset; - this.year = this._calcYear(rd); - } + if (this.offset !== 0) { + rd += this.offset; + this.year = this._calcYear(rd); + } - //console.log("PersDate.calcComponent: calculating for rd " + rd); - //console.log("PersDate.calcComponent: year is " + ret.year); - var yearStart = this.newRd({ - year: this.year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - remainder = rd - yearStart.getRataDie() + 1; + //console.log("PersDate.calcComponent: calculating for rd " + rd); + //console.log("PersDate.calcComponent: year is " + ret.year); + var yearStart = this.newRd({ + year: this.year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + remainder = rd - yearStart.getRataDie() + 1; - this.dayOfYear = remainder; + this.dayOfYear = remainder; - //console.log("PersDate.calcComponent: remainder is " + remainder); + //console.log("PersDate.calcComponent: remainder is " + remainder); - this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); - remainder -= PersianDate.cumMonthLengths[this.month-1]; + this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); + remainder -= PersianDate.cumMonthLengths[this.month-1]; - //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); + //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); - this.day = Math.floor(remainder); - remainder -= this.day; + this.day = Math.floor(remainder); + remainder -= this.day; - //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); + //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); - // now convert to milliseconds for the rest of the calculation - remainder = Math.round(remainder * 86400000); + // now convert to milliseconds for the rest of the calculation + remainder = Math.round(remainder * 86400000); - this.hour = Math.floor(remainder/3600000); - remainder -= this.hour * 3600000; + this.hour = Math.floor(remainder/3600000); + remainder -= this.hour * 3600000; - this.minute = Math.floor(remainder/60000); - remainder -= this.minute * 60000; + this.minute = Math.floor(remainder/60000); + remainder -= this.minute * 60000; - this.second = Math.floor(remainder/1000); - remainder -= this.second * 1000; + this.second = Math.floor(remainder/1000); + remainder -= this.second * 1000; - this.millisecond = remainder; + this.millisecond = remainder; }; /** @@ -367,8 +367,8 @@ PersianDate.prototype._calcDateComponents = function () { * @return {number} the day of the week */ PersianDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.getRataDie()); - return MathUtils.mod(rd-3, 7); + var rd = Math.floor(this.getRataDie()); + return MathUtils.mod(rd-3, 7); }; /** @@ -378,7 +378,7 @@ PersianDate.prototype.getDayOfWeek = function() { * @return {number} the ordinal day of the year */ PersianDate.prototype.getDayOfYear = function() { - return PersianDate.cumMonthLengths[this.month-1] + this.day; + return PersianDate.cumMonthLengths[this.month-1] + this.day; }; /** @@ -391,7 +391,7 @@ PersianDate.prototype.getDayOfYear = function() { * common era */ PersianDate.prototype.getEra = function() { - return (this.year < 1) ? -1 : 1; + return (this.year < 1) ? -1 : 1; }; /** @@ -400,7 +400,7 @@ PersianDate.prototype.getEra = function() { * @return {string} a string giving the name of the calendar */ PersianDate.prototype.getCalendar = function() { - return "persian"; + return "persian"; }; // register with the factory method diff --git a/js/lib/PhoneFmt.js b/js/lib/PhoneFmt.js index 62cfd9d599..9c929d4683 100644 --- a/js/lib/PhoneFmt.js +++ b/js/lib/PhoneFmt.js @@ -1,6 +1,6 @@ /* * phonefmt.js - Represent a phone number formatter. - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data phonefmt -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); var Locale = require("./Locale.js"); @@ -30,7 +30,7 @@ var PhoneLocale = require("./PhoneLocale.js"); /** * @class * Create a new phone number formatter object that formats numbers according to the parameters.

    - * + * * The options object can contain zero or more of the following parameters: * *

      @@ -38,434 +38,434 @@ var PhoneLocale = require("./PhoneLocale.js"); *
    • style the name of style to use to format numbers, or undefined to use the default style *
    • mcc the MCC of the country to use if the number is a local number and the country code is not known * - *
    • onLoad - a callback function to call when the locale data is fully loaded and the address has been - * parsed. When the onLoad option is given, the address formatter object + *
    • onLoad - a callback function to call when the locale data is fully loaded and the address has been + * parsed. When the onLoad option is given, the address formatter object * will attempt to load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    * * Some regions have more than one style of formatting, and the style parameter * selects which style the user prefers. An array of style names that this locale - * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. - * Example phone numbers can be retrieved for each style by calling + * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. + * Example phone numbers can be retrieved for each style by calling * {@link PhoneFmt.getStyleExample}. *

    * * If the MCC is given, numbers will be formatted in the manner of the country * specified by the MCC. If it is not given, but the locale is, the manner of * the country in the locale will be used. If neither the locale or MCC are not given, - * then the country of the current ilib locale is used. + * then the country of the current ilib locale is used. * * @constructor * @param {Object} options properties that control how this formatter behaves */ var PhoneFmt = function(options) { - this.sync = true; - this.styleName = 'default', - this.loadParams = {}; - - var locale = new Locale(); - - if (options) { - if (options.locale) { - locale = options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - if (options.loadParams) { - this.loadParams = options.loadParams; - } - - if (options.style) { - this.style = options.style; - } - } - - new PhoneLocale({ - locale: locale, - mcc: options && options.mcc, - countryCode: options && options.countryCode, - onLoad: ilib.bind(this, function (data) { - /** @type {PhoneLocale} */ - this.locale = data; - - new NumberingPlan({ - locale: this.locale, - sync: this.sync, - loadParms: this.loadParams, - onLoad: ilib.bind(this, function (plan) { - /** @type {NumberingPlan} */ - this.plan = plan; - - Utils.loadData({ - name: "phonefmt.json", - object: "PhoneFmt", - locale: this.locale, - sync: this.sync, - loadParams: JSUtils.merge(this.loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (fmtdata) { - this.fmtdata = fmtdata; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - }) - }); - }) - }); + this.sync = true; + this.styleName = 'default', + this.loadParams = {}; + + var locale = new Locale(); + + if (options) { + if (options.locale) { + locale = options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + if (options.loadParams) { + this.loadParams = options.loadParams; + } + + if (options.style) { + this.style = options.style; + } + } + + new PhoneLocale({ + locale: locale, + mcc: options && options.mcc, + countryCode: options && options.countryCode, + onLoad: ilib.bind(this, function (data) { + /** @type {PhoneLocale} */ + this.locale = data; + + new NumberingPlan({ + locale: this.locale, + sync: this.sync, + loadParms: this.loadParams, + onLoad: ilib.bind(this, function (plan) { + /** @type {NumberingPlan} */ + this.plan = plan; + + Utils.loadData({ + name: "phonefmt.json", + object: "PhoneFmt", + locale: this.locale, + sync: this.sync, + loadParams: JSUtils.merge(this.loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (fmtdata) { + this.fmtdata = fmtdata; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + }) + }); + }) + }); }; PhoneFmt.prototype = { - /** - * - * @protected - * @param {string} part - * @param {Object} formats - * @param {boolean} mustUseAll - */ - _substituteDigits: function(part, formats, mustUseAll) { - var formatString, - formatted = "", - partIndex = 0, - templates, - i; - - // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); - if (!part) { - return formatted; - } - - if (typeof(formats) === "object") { - templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; - if (part.length > templates.length) { - // too big, so just use last resort rule. - throw "part " + part + " is too big. We do not have a format template to format it."; - } - // use the format in this array that corresponds to the digit length of this - // part of the phone number - formatString = templates[part.length-1]; - // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); - } else { - formatString = formats; - } - - for (i = 0; i < formatString.length; i++) { - if (formatString.charAt(i) === "X") { - formatted += part.charAt(partIndex); - partIndex++; - } else { - formatted += formatString.charAt(i); - } - } - - if (mustUseAll && partIndex < part.length-1) { - // didn't use the whole thing in this format? Hmm... go to last resort rule - throw "too many digits in " + part + " for format " + formatString; - } - - return formatted; - }, - - /** - * Returns the style with the given name, or the default style if there - * is no style with that name. - * @protected - * @return {{example:string,whole:Object.,partial:Object.}|Object.} - */ - _getStyle: function (name, fmtdata) { - return fmtdata[name] || fmtdata["default"]; - }, - - /** - * Do the actual work of formatting the phone number starting at the given - * field in the regular field order. - * - * @param {!PhoneNumber} number - * @param {{ - * partial:boolean, - * style:string, - * mcc:string, - * locale:(string|Locale), - * sync:boolean, - * loadParams:Object, - * onLoad:function(string) - * }} options Parameters which control how to format the number - * @param {number} startField - */ - _doFormat: function(number, options, startField, locale, fmtdata, callback) { - var sync = true, - loadParams = {}, - temp, - templates, - fieldName, - countryCode, - isWhole, - style, - formatted = "", - styleTemplates, - lastFieldName; - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - style = this.style; // default style for this formatter - - // figure out what style to use for this type of number - if (number.countryCode) { - // dialing from outside the country - // check to see if it to a mobile number because they are often formatted differently - style = (number.mobilePrefix) ? "internationalmobile" : "international"; - } else if (number.mobilePrefix !== undefined) { - style = "mobile"; - } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { - // if there is a special format for service numbers, then use it - style = "service"; - } - - isWhole = (!options || !options.partial); - styleTemplates = this._getStyle(style, fmtdata); - - // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); - styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; - - for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { - fieldName = PhoneNumber._fieldOrder[i]; - // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); - if (number[fieldName] !== undefined) { - if (styleTemplates[fieldName] !== undefined) { - templates = styleTemplates[fieldName]; - if (fieldName === "trunkAccess") { - if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { - templates = "X"; - } - } - if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { - if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { - formatted += styleTemplates[lastFieldName].suffix; - } - } - lastFieldName = fieldName; - - // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); - temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); - // console.info("format: formatted is: " + temp); - formatted += temp; - - if (fieldName === "countryCode") { - // switch to the new country to format the rest of the number - countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 - - new PhoneLocale({ - locale: this.locale, - sync: sync, - loadParms: loadParams, - countryCode: countryCode, - onLoad: ilib.bind(this, function (locale) { - Utils.loadData({ - name: "phonefmt.json", - object: "PhoneFmt", - locale: locale, - sync: sync, - loadParams: JSUtils.merge(loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (fmtdata) { - // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); - - var subfmt = ""; - - this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { - subfmt = subformat; - if (typeof(callback) === 'function') { - callback(formatted + subformat); - } - }); - - formatted += subfmt; - }) - }); - }) - }); - return formatted; - } - } else { - //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); - // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates - formatted += number[fieldName]; - } - } - } - - if (typeof(callback) === 'function') { - callback(formatted); - } - - return formatted; - }, - - /** - * Format the parts of a phone number appropriately according to the settings in - * this formatter instance. - * - * The options can contain zero or more of these properties: - * - *

      - *
    • partial boolean which tells whether or not this phone number - * represents a partial number or not. The default is false, which means the number - * represents a whole number. - *
    • style style to use to format the number, if different from the - * default style or the style specified in the constructor - *
    • locale The locale with which to parse the number. This gives a clue as to which + /** + * + * @protected + * @param {string} part + * @param {Object} formats + * @param {boolean} mustUseAll + */ + _substituteDigits: function(part, formats, mustUseAll) { + var formatString, + formatted = "", + partIndex = 0, + templates, + i; + + // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); + if (!part) { + return formatted; + } + + if (typeof(formats) === "object") { + templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; + if (part.length > templates.length) { + // too big, so just use last resort rule. + throw "part " + part + " is too big. We do not have a format template to format it."; + } + // use the format in this array that corresponds to the digit length of this + // part of the phone number + formatString = templates[part.length-1]; + // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); + } else { + formatString = formats; + } + + for (i = 0; i < formatString.length; i++) { + if (formatString.charAt(i) === "X") { + formatted += part.charAt(partIndex); + partIndex++; + } else { + formatted += formatString.charAt(i); + } + } + + if (mustUseAll && partIndex < part.length-1) { + // didn't use the whole thing in this format? Hmm... go to last resort rule + throw "too many digits in " + part + " for format " + formatString; + } + + return formatted; + }, + + /** + * Returns the style with the given name, or the default style if there + * is no style with that name. + * @protected + * @return {{example:string,whole:Object.,partial:Object.}|Object.} + */ + _getStyle: function (name, fmtdata) { + return fmtdata[name] || fmtdata["default"]; + }, + + /** + * Do the actual work of formatting the phone number starting at the given + * field in the regular field order. + * + * @param {!PhoneNumber} number + * @param {{ + * partial:boolean, + * style:string, + * mcc:string, + * locale:(string|Locale), + * sync:boolean, + * loadParams:Object, + * onLoad:function(string) + * }} options Parameters which control how to format the number + * @param {number} startField + */ + _doFormat: function(number, options, startField, locale, fmtdata, callback) { + var sync = true, + loadParams = {}, + temp, + templates, + fieldName, + countryCode, + isWhole, + style, + formatted = "", + styleTemplates, + lastFieldName; + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + style = this.style; // default style for this formatter + + // figure out what style to use for this type of number + if (number.countryCode) { + // dialing from outside the country + // check to see if it to a mobile number because they are often formatted differently + style = (number.mobilePrefix) ? "internationalmobile" : "international"; + } else if (number.mobilePrefix !== undefined) { + style = "mobile"; + } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { + // if there is a special format for service numbers, then use it + style = "service"; + } + + isWhole = (!options || !options.partial); + styleTemplates = this._getStyle(style, fmtdata); + + // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); + styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; + + for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { + fieldName = PhoneNumber._fieldOrder[i]; + // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); + if (number[fieldName] !== undefined) { + if (styleTemplates[fieldName] !== undefined) { + templates = styleTemplates[fieldName]; + if (fieldName === "trunkAccess") { + if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { + templates = "X"; + } + } + if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { + if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { + formatted += styleTemplates[lastFieldName].suffix; + } + } + lastFieldName = fieldName; + + // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); + temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); + // console.info("format: formatted is: " + temp); + formatted += temp; + + if (fieldName === "countryCode") { + // switch to the new country to format the rest of the number + countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 + + new PhoneLocale({ + locale: this.locale, + sync: sync, + loadParms: loadParams, + countryCode: countryCode, + onLoad: ilib.bind(this, function (locale) { + Utils.loadData({ + name: "phonefmt.json", + object: "PhoneFmt", + locale: locale, + sync: sync, + loadParams: JSUtils.merge(loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (fmtdata) { + // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); + + var subfmt = ""; + + this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { + subfmt = subformat; + if (typeof(callback) === 'function') { + callback(formatted + subformat); + } + }); + + formatted += subfmt; + }) + }); + }) + }); + return formatted; + } + } else { + //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); + // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates + formatted += number[fieldName]; + } + } + } + + if (typeof(callback) === 'function') { + callback(formatted); + } + + return formatted; + }, + + /** + * Format the parts of a phone number appropriately according to the settings in + * this formatter instance. + * + * The options can contain zero or more of these properties: + * + *
        + *
      • partial boolean which tells whether or not this phone number + * represents a partial number or not. The default is false, which means the number + * represents a whole number. + *
      • style style to use to format the number, if different from the + * default style or the style specified in the constructor + *
      • locale The locale with which to parse the number. This gives a clue as to which * numbering plan to use. - *
      • mcc The mobile carrier code (MCC) associated with the carrier that the phone is + *
      • mcc The mobile carrier code (MCC) associated with the carrier that the phone is * currently connected to, if known. This also can give a clue as to which numbering plan to * use - *
      • onLoad - a callback function to call when the date format object is fully + *
      • onLoad - a callback function to call when the date format object is fully * loaded. When the onLoad option is given, the DateFmt object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - *
      • sync - tell whether to load any missing locale data synchronously or + *
      • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - *
      • loadParams - an object containing parameters to pass to the + *
      • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. - *
      - * - * The partial parameter specifies whether or not the phone number contains - * a partial phone number or if it is a whole phone number. A partial - * number is usually a number as the user is entering it with a dial pad. The - * reason is that certain types of phone numbers should be formatted differently - * depending on whether or not it represents a whole number. Specifically, SMS - * short codes are formatted differently.

      - * - * Example: a subscriber number of "48773" in the US would get formatted as: - * - *

        - *
      • partial: 487-73 (perhaps the user is in the process of typing a whole phone - * number such as 487-7379) - *
      • whole: 48773 (this is the entire SMS short code) - *
      - * - * Any place in the UI where the user types in phone numbers, such as the keypad in - * the phone app, should pass in partial: true to this formatting routine. All other - * places, such as the call log in the phone app, should pass in partial: false, or - * leave the partial flag out of the parameters entirely. - * - * @param {!PhoneNumber} number object containing the phone number to format - * @param {{ - * partial:boolean, - * style:string, - * mcc:string, - * locale:(string|Locale), - * sync:boolean, - * loadParams:Object, - * onLoad:function(string) - * }} options Parameters which control how to format the number - * @return {string} Returns the formatted phone number as a string. - */ - format: function (number, options) { - var formatted = "", - callback; - - callback = options && options.onLoad; - - try { - this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { - formatted = fmt; - - if (typeof(callback) === 'function') { - callback(fmt); - } - }); - } catch (e) { - if (typeof(e) === 'string') { - // console.warn("caught exception: " + e + ". Using last resort rule."); - // if there was some exception, use this last resort rule - formatted = ""; - for (var field in PhoneNumber._fieldOrder) { - if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { - // just concatenate without any formatting - formatted += number[PhoneNumber._fieldOrder[field]]; - if (PhoneNumber._fieldOrder[field] === 'countryCode') { - formatted += ' '; // fix for NOV-107894 - } - } - } - } else { - throw e; - } - - if (typeof(callback) === 'function') { - callback(formatted); - } - } - return formatted; - }, - - /** - * Return an array of names of all available styles that can be used with the current - * formatter. - * @return {Array.} an array of names of styles that are supported by this formatter - */ - getAvailableStyles: function () { - var ret = [], - style; - - if (this.fmtdata) { - for (style in this.fmtdata) { - if (this.fmtdata[style].example) { - ret.push(style); - } - } - } - return ret; - }, - - /** - * Return an example phone number formatted with the given style. - * - * @param {string|undefined} style style to get an example of, or undefined to use - * the current default style for this formatter - * @return {string|undefined} an example phone number formatted according to the - * given style, or undefined if the style is not recognized or does not have an - * example - */ - getStyleExample: function (style) { - return this.fmtdata[style].example || undefined; - } + *
    + * + * The partial parameter specifies whether or not the phone number contains + * a partial phone number or if it is a whole phone number. A partial + * number is usually a number as the user is entering it with a dial pad. The + * reason is that certain types of phone numbers should be formatted differently + * depending on whether or not it represents a whole number. Specifically, SMS + * short codes are formatted differently.

    + * + * Example: a subscriber number of "48773" in the US would get formatted as: + * + *

      + *
    • partial: 487-73 (perhaps the user is in the process of typing a whole phone + * number such as 487-7379) + *
    • whole: 48773 (this is the entire SMS short code) + *
    + * + * Any place in the UI where the user types in phone numbers, such as the keypad in + * the phone app, should pass in partial: true to this formatting routine. All other + * places, such as the call log in the phone app, should pass in partial: false, or + * leave the partial flag out of the parameters entirely. + * + * @param {!PhoneNumber} number object containing the phone number to format + * @param {{ + * partial:boolean, + * style:string, + * mcc:string, + * locale:(string|Locale), + * sync:boolean, + * loadParams:Object, + * onLoad:function(string) + * }} options Parameters which control how to format the number + * @return {string} Returns the formatted phone number as a string. + */ + format: function (number, options) { + var formatted = "", + callback; + + callback = options && options.onLoad; + + try { + this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { + formatted = fmt; + + if (typeof(callback) === 'function') { + callback(fmt); + } + }); + } catch (e) { + if (typeof(e) === 'string') { + // console.warn("caught exception: " + e + ". Using last resort rule."); + // if there was some exception, use this last resort rule + formatted = ""; + for (var field in PhoneNumber._fieldOrder) { + if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { + // just concatenate without any formatting + formatted += number[PhoneNumber._fieldOrder[field]]; + if (PhoneNumber._fieldOrder[field] === 'countryCode') { + formatted += ' '; // fix for NOV-107894 + } + } + } + } else { + throw e; + } + + if (typeof(callback) === 'function') { + callback(formatted); + } + } + return formatted; + }, + + /** + * Return an array of names of all available styles that can be used with the current + * formatter. + * @return {Array.} an array of names of styles that are supported by this formatter + */ + getAvailableStyles: function () { + var ret = [], + style; + + if (this.fmtdata) { + for (style in this.fmtdata) { + if (this.fmtdata[style].example) { + ret.push(style); + } + } + } + return ret; + }, + + /** + * Return an example phone number formatted with the given style. + * + * @param {string|undefined} style style to get an example of, or undefined to use + * the current default style for this formatter + * @return {string|undefined} an example phone number formatted according to the + * given style, or undefined if the style is not recognized or does not have an + * example + */ + getStyleExample: function (style) { + return this.fmtdata[style].example || undefined; + } }; module.exports = PhoneFmt; \ No newline at end of file diff --git a/js/lib/PhoneGeoLocator.js b/js/lib/PhoneGeoLocator.js index bbf731ba37..00e78d18f3 100644 --- a/js/lib/PhoneGeoLocator.js +++ b/js/lib/PhoneGeoLocator.js @@ -1,6 +1,6 @@ /* * PhoneGeoLocator.js - Represent a phone number geolocator object. - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data iddarea area extarea extstates phoneres -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -31,622 +31,622 @@ var ResBundle = require("./ResBundle.js"); /** * @class * Create an instance that can geographically locate a phone number.

    - * + * * The location of the number is calculated according to the following rules: - * + * *

      - *
    1. If the areaCode property is undefined or empty, or if the number specifies a - * country code for which we do not have information, then the area property may be + *
    2. If the areaCode property is undefined or empty, or if the number specifies a + * country code for which we do not have information, then the area property may be * missing from the returned object. In this case, only the country object will be returned. - * - *
    3. If there is no area code, but there is a mobile prefix, service code, or emergency + * + *
    4. If there is no area code, but there is a mobile prefix, service code, or emergency * code, then a fixed string indicating the type of number will be returned. - * + * *
    5. The country object is filled out according to the countryCode property of the phone - * number. - * + * number. + * *
    6. If the phone number does not have an explicit country code, the MCC will be used if - * it is available. The country code can be gleaned directly from the MCC. If the MCC - * of the carrier to which the phone is currently connected is available, it should be + * it is available. The country code can be gleaned directly from the MCC. If the MCC + * of the carrier to which the phone is currently connected is available, it should be * passed in so that local phone numbers will look correct. - * - *
    7. If the country's dialling plan mandates a fixed length for phone numbers, and a + * + *
    8. If the country's dialling plan mandates a fixed length for phone numbers, and a * particular number exceeds that length, then the area code will not be given on the * assumption that the number has problems in the first place and we cannot guess * correctly. *
    - * + * * The returned area property varies in specificity according * to the locale. In North America, the area is no finer than large parts of states * or provinces. In Germany and the UK, the area can be as fine as small towns.

    - * + * * If the number passed in is invalid, no geolocation will be performed. If the location * information about the country where the phone number is located is not available, * then the area information will be missing and only the country will be available.

    - * + * * The options parameter can contain any one of the following properties: - * + * *

      *
    • locale The locale parameter is used to load translations of the names of regions and - * areas if available. For example, if the locale property is given as "en-US" (English for USA), + * areas if available. For example, if the locale property is given as "en-US" (English for USA), * but the phone number being geolocated is in Germany, then this class would return the the names - * of the country (Germany) and region inside of Germany in English instead of German. That is, a + * of the country (Germany) and region inside of Germany in English instead of German. That is, a * phone number in Munich and return the country "Germany" and the area code "Munich" - * instead of "Deutschland" and "München". The default display locale is the current ilib locale. - * If translations are not available, the region and area names are given in English, which should + * instead of "Deutschland" and "München". The default display locale is the current ilib locale. + * If translations are not available, the region and area names are given in English, which should * always be available. *
    • mcc The mcc of the current mobile carrier, if known. - * + * *
    • onLoad - a callback function to call when the data for the - * locale is fully loaded. When the onLoad option is given, this object + * locale is fully loaded. When the onLoad option is given, this object * will attempt to load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * + * * @constructor * @param {Object} options parameters controlling the geolocation of the phone number. */ var PhoneGeoLocator = function(options) { - var sync = true, - loadParams = {}, - locale = ilib.getLocale(); - - if (options) { - if (options.locale) { - locale = options.locale; - } - - if (typeof(options.sync) === 'boolean') { - sync = options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - new PhoneLocale({ - locale: locale, - mcc: options && options.mcc, - countryCode: options && options.countryCode, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (loc) { - this.locale = loc; - new NumberingPlan({ - locale: this.locale, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (plan) { - this.plan = plan; - - new ResBundle({ - locale: this.locale, - name: "phoneres", - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function (rb) { - this.rb = rb; - - Utils.loadData({ - name: "iddarea.json", - object: "PhoneGeoLocator", - nonlocale: true, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (data) { - this.regiondata = data; - Utils.loadData({ - name: "area.json", - object: "PhoneGeoLocator", - locale: this.locale, - sync: sync, - loadParams: JSUtils.merge(loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (areadata) { - this.areadata = areadata; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - }) - }); - }) - }); - }) - }); - }) - }); + var sync = true, + loadParams = {}, + locale = ilib.getLocale(); + + if (options) { + if (options.locale) { + locale = options.locale; + } + + if (typeof(options.sync) === 'boolean') { + sync = options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + new PhoneLocale({ + locale: locale, + mcc: options && options.mcc, + countryCode: options && options.countryCode, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (loc) { + this.locale = loc; + new NumberingPlan({ + locale: this.locale, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (plan) { + this.plan = plan; + + new ResBundle({ + locale: this.locale, + name: "phoneres", + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function (rb) { + this.rb = rb; + + Utils.loadData({ + name: "iddarea.json", + object: "PhoneGeoLocator", + nonlocale: true, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (data) { + this.regiondata = data; + Utils.loadData({ + name: "area.json", + object: "PhoneGeoLocator", + locale: this.locale, + sync: sync, + loadParams: JSUtils.merge(loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (areadata) { + this.areadata = areadata; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + }) + }); + }) + }); + }) + }); + }) + }); }; PhoneGeoLocator.prototype = { - /** - * @private - * - * Used for locales where the area code is very general, and you need to add in - * the initial digits of the subscriber number in order to get the area - * - * @param {string} number - * @param {Object} stateTable - */ - _parseAreaAndSubscriber: function (number, stateTable) { - var ch, - i, - handlerMethod, - newState, - consumed, - lastLeaf, - currentState, - dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java - - if (!number || !stateTable) { - // can't parse anything - return undefined; - } - - //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); - - currentState = stateTable; - i = 0; - while (i < number.length) { - ch = PhoneNumber._getCharacterCode(number.charAt(i)); - if (ch >= 0) { - // newState = stateData.states[state][ch]; - newState = currentState.s && currentState.s[ch]; - - if (!newState && currentState.s && currentState.s[dot]) { - newState = currentState.s[dot]; - } - - if (typeof(newState) === 'object') { - if (typeof(newState.l) !== 'undefined') { - // save for latter if needed - lastLeaf = newState; - consumed = i; - } - // console.info("recognized digit " + ch + " continuing..."); - // recognized digit, so continue parsing - currentState = newState; - i++; - } else { - if (typeof(newState) === 'undefined' || newState === 0) { - // this is possibly a look-ahead and it didn't work... - // so fall back to the last leaf and use that as the - // final state - newState = lastLeaf; - i = consumed; - } - - if ((typeof(newState) === 'number' && newState) || - (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { - // final state - var stateNumber = typeof(newState) === 'number' ? newState : newState.l; - handlerMethod = PhoneNumber._states[stateNumber]; - - //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); - - return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; - } else { - // failed parse. Either no last leaf to fall back to, or there was an explicit - // zero in the table - break; - } - } - } else if (ch === -1) { - // non-transition character, continue parsing in the same state - i++; - } else { - // should not happen - // console.info("skipping character " + ch); - // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. - i++; - } - } - return undefined; - }, - /** - * @private - * @param prefix - * @param table - * @returns - */ - _matchPrefix: function(prefix, table) { - var i, matchedDot, matchesWithDots = []; - - if (table[prefix]) { - return table[prefix]; - } - for (var entry in table) { - if (entry && typeof(entry) === 'string') { - i = 0; - matchedDot = false; - while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { - if (entry.charAt(i) === '.') { - matchedDot = true; - } - i++; - } - if (i >= entry.length) { - if (matchedDot) { - matchesWithDots.push(entry); - } else { - return table[entry]; - } - } - } - } - - // match entries with dots last, so sort the matches so that the entry with the - // most dots sorts last. The entry that ends up at the beginning of the list is - // the best match because it has the fewest dots - if (matchesWithDots.length > 0) { - matchesWithDots.sort(function (left, right) { - return (right < left) ? -1 : ((left < right) ? 1 : 0); - }); - return table[matchesWithDots[0]]; - } - - return undefined; - }, - /** - * @private - * @param number - * @param data - * @param locale - * @param plan - * @param options - * @returns {Object} - */ - _getAreaInfo: function(number, data, locale, plan, options) { - var sync = true, - ret = {}, - countryCode, - areaInfo, - temp, - areaCode, - geoTable, - tempNumber, - prefix; - - if (options && typeof(options.sync) === 'boolean') { - sync = options.sync; - } - - prefix = number.areaCode || number.serviceCode; - geoTable = data; - - if (prefix !== undefined) { - if (plan.getExtendedAreaCode()) { - // for countries where the area code is very general and large, and you need a few initial - // digits of the subscriber number in order find the actual area - tempNumber = prefix + number.subscriberNumber; - tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 - - Utils.loadData({ - name: "extarea.json", - object: "PhoneGeoLocator", - locale: locale, - sync: sync, - loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), - callback: ilib.bind(this, function (data) { - this.extarea = data; - Utils.loadData({ - name: "extstates.json", - object: "PhoneGeoLocator", - locale: locale, - sync: sync, - loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), - callback: ilib.bind(this, function (data) { - this.extstates = data; - geoTable = this.extarea; - if (this.extarea && this.extstates) { - prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); - } - - if (!prefix) { - // not a recognized prefix, so now try the general table - geoTable = this.areadata; - prefix = number.areaCode || number.serviceCode; - } - - if ((!plan.fieldLengths || - plan.getFieldLength('maxLocalLength') === undefined || - !number.subscriberNumber || - number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { - areaInfo = this._matchPrefix(prefix, geoTable); - if (areaInfo && areaInfo.sn && areaInfo.ln) { - //console.log("Found areaInfo " + JSON.stringify(areaInfo)); - ret.area = { - sn: this.rb.getString(areaInfo.sn).toString(), - ln: this.rb.getString(areaInfo.ln).toString() - }; - } - } - }) - }); - }) - }); - - } else if (!plan || - plan.getFieldLength('maxLocalLength') === undefined || - !number.subscriberNumber || - number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { - if (geoTable) { - areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); - areaInfo = this._matchPrefix(areaCode, geoTable); - - if (areaInfo && areaInfo.sn && areaInfo.ln) { - ret.area = { - sn: this.rb.getString(areaInfo.sn).toString(), - ln: this.rb.getString(areaInfo.ln).toString() - }; - } else if (number.serviceCode) { - ret.area = { - sn: this.rb.getString("Service Number").toString(), - ln: this.rb.getString("Service Number").toString() - }; - } - } else { - countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); - if (countryCode !== "0" && this.regiondata) { - temp = this.regiondata[countryCode]; - if (temp && temp.sn) { - ret.country = { - sn: this.rb.getString(temp.sn).toString(), - ln: this.rb.getString(temp.ln).toString(), - code: this.locale.getRegion() - }; - } - } - } - } else { - countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); - if (countryCode !== "0" && this.regiondata) { - temp = this.regiondata[countryCode]; - if (temp && temp.sn) { - ret.country = { - sn: this.rb.getString(temp.sn).toString(), - ln: this.rb.getString(temp.ln).toString(), - code: this.locale.getRegion() - }; - } - } - } - - } else if (number.mobilePrefix) { - ret.area = { - sn: this.rb.getString("Mobile Number").toString(), - ln: this.rb.getString("Mobile Number").toString() - }; - } else if (number.emergency) { - ret.area = { - sn: this.rb.getString("Emergency Services Number").toString(), - ln: this.rb.getString("Emergency Services Number").toString() - }; - } - - return ret; - }, - /** - * Returns a the location of the given phone number, if known. - * The returned object has 2 properties, each of which has an sn (short name) - * and an ln (long name) string. Additionally, the country code, if given, - * includes the 2 letter ISO code for the recognized country. - * { - * "country": { - * "sn": "North America", - * "ln": "North America and the Caribbean Islands", - * "code": "us" - * }, - * "area": { - * "sn": "California", - * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" - * } - * } - * - * The location name is subject to the following rules: - * - * If the areaCode property is undefined or empty, or if the number specifies a - * country code for which we do not have information, then the area property may be - * missing from the returned object. In this case, only the country object will be returned. - * - * If there is no area code, but there is a mobile prefix, service code, or emergency - * code, then a fixed string indicating the type of number will be returned. - * - * The country object is filled out according to the countryCode property of the phone - * number. - * - * If the phone number does not have an explicit country code, the MCC will be used if - * it is available. The country code can be gleaned directly from the MCC. If the MCC - * of the carrier to which the phone is currently connected is available, it should be - * passed in so that local phone numbers will look correct. - * - * If the country's dialling plan mandates a fixed length for phone numbers, and a - * particular number exceeds that length, then the area code will not be given on the - * assumption that the number has problems in the first place and we cannot guess - * correctly. - * - * The returned area property varies in specificity according - * to the locale. In North America, the area is no finer than large parts of states - * or provinces. In Germany and the UK, the area can be as fine as small towns. - * - * The strings returned from this function are already localized to the - * given locale, and thus are ready for display to the user. - * - * If the number passed in is invalid, an empty object is returned. If the location - * information about the country where the phone number is located is not available, - * then the area information will be missing and only the country will be returned. + /** + * @private + * + * Used for locales where the area code is very general, and you need to add in + * the initial digits of the subscriber number in order to get the area + * + * @param {string} number + * @param {Object} stateTable + */ + _parseAreaAndSubscriber: function (number, stateTable) { + var ch, + i, + handlerMethod, + newState, + consumed, + lastLeaf, + currentState, + dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java + + if (!number || !stateTable) { + // can't parse anything + return undefined; + } + + //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); + + currentState = stateTable; + i = 0; + while (i < number.length) { + ch = PhoneNumber._getCharacterCode(number.charAt(i)); + if (ch >= 0) { + // newState = stateData.states[state][ch]; + newState = currentState.s && currentState.s[ch]; + + if (!newState && currentState.s && currentState.s[dot]) { + newState = currentState.s[dot]; + } + + if (typeof(newState) === 'object') { + if (typeof(newState.l) !== 'undefined') { + // save for latter if needed + lastLeaf = newState; + consumed = i; + } + // console.info("recognized digit " + ch + " continuing..."); + // recognized digit, so continue parsing + currentState = newState; + i++; + } else { + if (typeof(newState) === 'undefined' || newState === 0) { + // this is possibly a look-ahead and it didn't work... + // so fall back to the last leaf and use that as the + // final state + newState = lastLeaf; + i = consumed; + } + + if ((typeof(newState) === 'number' && newState) || + (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { + // final state + var stateNumber = typeof(newState) === 'number' ? newState : newState.l; + handlerMethod = PhoneNumber._states[stateNumber]; + + //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); + + return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; + } else { + // failed parse. Either no last leaf to fall back to, or there was an explicit + // zero in the table + break; + } + } + } else if (ch === -1) { + // non-transition character, continue parsing in the same state + i++; + } else { + // should not happen + // console.info("skipping character " + ch); + // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. + i++; + } + } + return undefined; + }, + /** + * @private + * @param prefix + * @param table + * @returns + */ + _matchPrefix: function(prefix, table) { + var i, matchedDot, matchesWithDots = []; + + if (table[prefix]) { + return table[prefix]; + } + for (var entry in table) { + if (entry && typeof(entry) === 'string') { + i = 0; + matchedDot = false; + while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { + if (entry.charAt(i) === '.') { + matchedDot = true; + } + i++; + } + if (i >= entry.length) { + if (matchedDot) { + matchesWithDots.push(entry); + } else { + return table[entry]; + } + } + } + } + + // match entries with dots last, so sort the matches so that the entry with the + // most dots sorts last. The entry that ends up at the beginning of the list is + // the best match because it has the fewest dots + if (matchesWithDots.length > 0) { + matchesWithDots.sort(function (left, right) { + return (right < left) ? -1 : ((left < right) ? 1 : 0); + }); + return table[matchesWithDots[0]]; + } + + return undefined; + }, + /** + * @private + * @param number + * @param data + * @param locale + * @param plan + * @param options + * @returns {Object} + */ + _getAreaInfo: function(number, data, locale, plan, options) { + var sync = true, + ret = {}, + countryCode, + areaInfo, + temp, + areaCode, + geoTable, + tempNumber, + prefix; + + if (options && typeof(options.sync) === 'boolean') { + sync = options.sync; + } + + prefix = number.areaCode || number.serviceCode; + geoTable = data; + + if (prefix !== undefined) { + if (plan.getExtendedAreaCode()) { + // for countries where the area code is very general and large, and you need a few initial + // digits of the subscriber number in order find the actual area + tempNumber = prefix + number.subscriberNumber; + tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 + + Utils.loadData({ + name: "extarea.json", + object: "PhoneGeoLocator", + locale: locale, + sync: sync, + loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), + callback: ilib.bind(this, function (data) { + this.extarea = data; + Utils.loadData({ + name: "extstates.json", + object: "PhoneGeoLocator", + locale: locale, + sync: sync, + loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), + callback: ilib.bind(this, function (data) { + this.extstates = data; + geoTable = this.extarea; + if (this.extarea && this.extstates) { + prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); + } + + if (!prefix) { + // not a recognized prefix, so now try the general table + geoTable = this.areadata; + prefix = number.areaCode || number.serviceCode; + } + + if ((!plan.fieldLengths || + plan.getFieldLength('maxLocalLength') === undefined || + !number.subscriberNumber || + number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { + areaInfo = this._matchPrefix(prefix, geoTable); + if (areaInfo && areaInfo.sn && areaInfo.ln) { + //console.log("Found areaInfo " + JSON.stringify(areaInfo)); + ret.area = { + sn: this.rb.getString(areaInfo.sn).toString(), + ln: this.rb.getString(areaInfo.ln).toString() + }; + } + } + }) + }); + }) + }); + + } else if (!plan || + plan.getFieldLength('maxLocalLength') === undefined || + !number.subscriberNumber || + number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { + if (geoTable) { + areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); + areaInfo = this._matchPrefix(areaCode, geoTable); + + if (areaInfo && areaInfo.sn && areaInfo.ln) { + ret.area = { + sn: this.rb.getString(areaInfo.sn).toString(), + ln: this.rb.getString(areaInfo.ln).toString() + }; + } else if (number.serviceCode) { + ret.area = { + sn: this.rb.getString("Service Number").toString(), + ln: this.rb.getString("Service Number").toString() + }; + } + } else { + countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); + if (countryCode !== "0" && this.regiondata) { + temp = this.regiondata[countryCode]; + if (temp && temp.sn) { + ret.country = { + sn: this.rb.getString(temp.sn).toString(), + ln: this.rb.getString(temp.ln).toString(), + code: this.locale.getRegion() + }; + } + } + } + } else { + countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); + if (countryCode !== "0" && this.regiondata) { + temp = this.regiondata[countryCode]; + if (temp && temp.sn) { + ret.country = { + sn: this.rb.getString(temp.sn).toString(), + ln: this.rb.getString(temp.ln).toString(), + code: this.locale.getRegion() + }; + } + } + } + + } else if (number.mobilePrefix) { + ret.area = { + sn: this.rb.getString("Mobile Number").toString(), + ln: this.rb.getString("Mobile Number").toString() + }; + } else if (number.emergency) { + ret.area = { + sn: this.rb.getString("Emergency Services Number").toString(), + ln: this.rb.getString("Emergency Services Number").toString() + }; + } + + return ret; + }, + /** + * Returns a the location of the given phone number, if known. + * The returned object has 2 properties, each of which has an sn (short name) + * and an ln (long name) string. Additionally, the country code, if given, + * includes the 2 letter ISO code for the recognized country. + * { + * "country": { + * "sn": "North America", + * "ln": "North America and the Caribbean Islands", + * "code": "us" + * }, + * "area": { + * "sn": "California", + * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" + * } + * } + * + * The location name is subject to the following rules: + * + * If the areaCode property is undefined or empty, or if the number specifies a + * country code for which we do not have information, then the area property may be + * missing from the returned object. In this case, only the country object will be returned. + * + * If there is no area code, but there is a mobile prefix, service code, or emergency + * code, then a fixed string indicating the type of number will be returned. + * + * The country object is filled out according to the countryCode property of the phone + * number. + * + * If the phone number does not have an explicit country code, the MCC will be used if + * it is available. The country code can be gleaned directly from the MCC. If the MCC + * of the carrier to which the phone is currently connected is available, it should be + * passed in so that local phone numbers will look correct. + * + * If the country's dialling plan mandates a fixed length for phone numbers, and a + * particular number exceeds that length, then the area code will not be given on the + * assumption that the number has problems in the first place and we cannot guess + * correctly. + * + * The returned area property varies in specificity according + * to the locale. In North America, the area is no finer than large parts of states + * or provinces. In Germany and the UK, the area can be as fine as small towns. + * + * The strings returned from this function are already localized to the + * given locale, and thus are ready for display to the user. + * + * If the number passed in is invalid, an empty object is returned. If the location + * information about the country where the phone number is located is not available, + * then the area information will be missing and only the country will be returned. * - * The options parameter can contain any one of the following properties: - * - *
      - *
    • locale The locale parameter is used to load translations of the names of regions and - * areas if available. For example, if the locale property is given as "en-US" (English for USA), - * but the phone number being geolocated is in Germany, then this class would return the the names - * of the country (Germany) and region inside of Germany in English instead of German. That is, a - * phone number in Munich and return the country "Germany" and the area code "Munich" - * instead of "Deutschland" and "München". The default display locale is the current ilib locale. - * If translations are not available, the region and area names are given in English, which should - * always be available. - *
    • mcc The mcc of the current mobile carrier, if known. - * - *
    • onLoad - a callback function to call when the data for the - * locale is fully loaded. When the onLoad option is given, this object - * will attempt to load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the - * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or - * asynchronously. If this option is given as "false", then the "onLoad" - * callback must be given, as the instance returned from this constructor will - * not be usable for a while. - * - *
    • loadParams - an object containing parameters to pass to the - * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object - * may contain any property/value pairs as long as the calling code is in - * agreement with the loader callback function as to what those parameters mean. - *
    - * - * @param {PhoneNumber} number phone number to locate - * @param {Object} options options governing the way this ares is loaded - * @return {Object} an object - * that describes the country and the area in that country corresponding to this - * phone number. Each of the country and area contain a short name (sn) and long - * name (ln) that describes the location. - */ - locate: function(number, options) { - var loadParams = {}, - ret = {}, - region, - countryCode, - temp, - plan, - areaResult, - phoneLoc = this.locale, - sync = true; - - if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { - return ret; - } - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); - region = this.locale.getRegion(); - if (number.countryCode !== undefined && this.regiondata) { - countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); - temp = this.regiondata[countryCode]; - phoneLoc = number.destinationLocale; - plan = number.destinationPlan; - ret.country = { - sn: this.rb.getString(temp.sn).toString(), - ln: this.rb.getString(temp.ln).toString(), - code: phoneLoc.getRegion() - }; - } - - if (!plan) { - plan = this.plan; - } - - Utils.loadData({ - name: "area.json", - object: "PhoneGeoLocator", - locale: phoneLoc, - sync: sync, - loadParams: JSUtils.merge(loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (areadata) { - if (areadata) { - this.areadata = areadata; - } - areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); - ret = JSUtils.merge(ret, areaResult); - - if (ret.country === undefined) { - countryCode = number.locale._mapRegiontoCC(region); - - if (countryCode !== "0" && this.regiondata) { - temp = this.regiondata[countryCode]; - if (temp && temp.sn) { - ret.country = { - sn: this.rb.getString(temp.sn).toString(), - ln: this.rb.getString(temp.ln).toString(), - code: this.locale.getRegion() - }; - } - } - } - }) - }); - - return ret; - }, - - /** - * Returns a string that describes the ISO-3166-2 country code of the given phone - * number.

    - * - * If the phone number is a local phone number and does not contain - * any country information, this routine will return the region for the current - * formatter instance. + * The options parameter can contain any one of the following properties: + * + *

      + *
    • locale The locale parameter is used to load translations of the names of regions and + * areas if available. For example, if the locale property is given as "en-US" (English for USA), + * but the phone number being geolocated is in Germany, then this class would return the the names + * of the country (Germany) and region inside of Germany in English instead of German. That is, a + * phone number in Munich and return the country "Germany" and the area code "Munich" + * instead of "Deutschland" and "München". The default display locale is the current ilib locale. + * If translations are not available, the region and area names are given in English, which should + * always be available. + *
    • mcc The mcc of the current mobile carrier, if known. + * + *
    • onLoad - a callback function to call when the data for the + * locale is fully loaded. When the onLoad option is given, this object + * will attempt to load any missing locale data using the ilib loader callback. + * When the constructor is done (even if the data is already preassembled), the + * onLoad function is called with the current instance as a parameter, so this + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
    • sync - tell whether to load any missing locale data synchronously or + * asynchronously. If this option is given as "false", then the "onLoad" + * callback must be given, as the instance returned from this constructor will + * not be usable for a while. + * + *
    • loadParams - an object containing parameters to pass to the + * loader callback function when locale data is missing. The parameters are not + * interpretted or modified in any way. They are simply passed along. The object + * may contain any property/value pairs as long as the calling code is in + * agreement with the loader callback function as to what those parameters mean. + *
    * - * @param {PhoneNumber} number An PhoneNumber instance - * @return {string} - */ - country: function(number) { - var countryCode, - region, - phoneLoc; - - if (!number || !(number instanceof PhoneNumber)) { - return ""; - } - - phoneLoc = number.locale; - - region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || - (number.locale && number.locale.region) || - phoneLoc.locale.getRegion() || - this.locale.getRegion(); - - countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); - - if (number.areaCode) { - region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); - } else if (countryCode === "33" && number.serviceCode) { - // french departments are in the service code, not the area code - region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); - } - return region; - } + * @param {PhoneNumber} number phone number to locate + * @param {Object} options options governing the way this ares is loaded + * @return {Object} an object + * that describes the country and the area in that country corresponding to this + * phone number. Each of the country and area contain a short name (sn) and long + * name (ln) that describes the location. + */ + locate: function(number, options) { + var loadParams = {}, + ret = {}, + region, + countryCode, + temp, + plan, + areaResult, + phoneLoc = this.locale, + sync = true; + + if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { + return ret; + } + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); + region = this.locale.getRegion(); + if (number.countryCode !== undefined && this.regiondata) { + countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); + temp = this.regiondata[countryCode]; + phoneLoc = number.destinationLocale; + plan = number.destinationPlan; + ret.country = { + sn: this.rb.getString(temp.sn).toString(), + ln: this.rb.getString(temp.ln).toString(), + code: phoneLoc.getRegion() + }; + } + + if (!plan) { + plan = this.plan; + } + + Utils.loadData({ + name: "area.json", + object: "PhoneGeoLocator", + locale: phoneLoc, + sync: sync, + loadParams: JSUtils.merge(loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (areadata) { + if (areadata) { + this.areadata = areadata; + } + areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); + ret = JSUtils.merge(ret, areaResult); + + if (ret.country === undefined) { + countryCode = number.locale._mapRegiontoCC(region); + + if (countryCode !== "0" && this.regiondata) { + temp = this.regiondata[countryCode]; + if (temp && temp.sn) { + ret.country = { + sn: this.rb.getString(temp.sn).toString(), + ln: this.rb.getString(temp.ln).toString(), + code: this.locale.getRegion() + }; + } + } + } + }) + }); + + return ret; + }, + + /** + * Returns a string that describes the ISO-3166-2 country code of the given phone + * number.

    + * + * If the phone number is a local phone number and does not contain + * any country information, this routine will return the region for the current + * formatter instance. + * + * @param {PhoneNumber} number An PhoneNumber instance + * @return {string} + */ + country: function(number) { + var countryCode, + region, + phoneLoc; + + if (!number || !(number instanceof PhoneNumber)) { + return ""; + } + + phoneLoc = number.locale; + + region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || + (number.locale && number.locale.region) || + phoneLoc.locale.getRegion() || + this.locale.getRegion(); + + countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); + + if (number.areaCode) { + region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); + } else if (countryCode === "33" && number.serviceCode) { + // french departments are in the service code, not the area code + region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); + } + return region; + } }; module.exports = PhoneGeoLocator; diff --git a/js/lib/PhoneHandlerFactory.js b/js/lib/PhoneHandlerFactory.js index 516a47ff99..069e8f1a8f 100644 --- a/js/lib/PhoneHandlerFactory.js +++ b/js/lib/PhoneHandlerFactory.js @@ -1,6 +1,6 @@ /* * handler.js - Handle phone number parse states - * + * * Copyright © 2014-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,498 +24,498 @@ * @constructor */ var PhoneHandler = function () { - return this; + return this; }; PhoneHandler.prototype = { - /** - * @private - * @param {string} number phone number - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - processSubscriberNumber: function(number, fields, regionSettings) { - var last; - - last = number.search(/[xwtp,;]/i); // last digit of the local number - - if (last > -1) { - if (last > 0) { - fields.subscriberNumber = number.substring(0, last); - } - // strip x's which are there to indicate a break between the local subscriber number and the extension, but - // are not themselves a dialable character - fields.extension = number.substring(last).replace('x', ''); - } else { - if (number.length) { - fields.subscriberNumber = number; - } - } - - if (regionSettings.plan.getFieldLength('maxLocalLength') && - fields.subscriberNumber && - fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { - fields.invalid = true; - } - }, - /** - * @private - * @param {string} fieldName - * @param {number} length length of phone number - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - * @param {boolean} noExtractTrunk - */ - processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { - var ret, end; - - if (length !== undefined && length > 0) { - // fixed length - end = length; - if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { - end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code - } - } else { - // variable length - // the setting is the negative of the length to add, so subtract to make it positive - end = currentChar + 1 - length; - } - - if (fields[fieldName] !== undefined) { - // we have a spurious recognition, because this number already contains that field! So, just put - // everything into the subscriberNumber as the default - this.processSubscriberNumber(number, fields, regionSettings); - } else { - fields[fieldName] = number.substring(0, end); - if (number.length > end) { - this.processSubscriberNumber(number.substring(end), fields, regionSettings); - } - } - - ret = { - number: "" - }; - - return ret; - }, - /** - * @private - * @param {string} fieldName - * @param {number} length length of phone number - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - processField: function(fieldName, length, number, currentChar, fields, regionSettings) { - var ret = {}, end; - - if (length !== undefined && length > 0) { - // fixed length - end = length; - if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { - end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code - } - } else { - // variable length - // the setting is the negative of the length to add, so subtract to make it positive - end = currentChar + 1 - length; - } - - if (fields[fieldName] !== undefined) { - // we have a spurious recognition, because this number already contains that field! So, just put - // everything into the subscriberNumber as the default - this.processSubscriberNumber(number, fields, regionSettings); - ret.number = ""; - } else { - fields[fieldName] = number.substring(0, end); - ret.number = (number.length > end) ? number.substring(end) : ""; - } - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - trunk: function(number, currentChar, fields, regionSettings) { - var ret, trunkLength; - - if (fields.trunkAccess !== undefined) { - // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. - this.processSubscriberNumber(number, fields, regionSettings); - number = ""; - } else { - trunkLength = regionSettings.plan.getTrunkCode().length; - fields.trunkAccess = number.substring(0, trunkLength); - number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; - } - - ret = { - number: number - }; - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - plus: function(number, currentChar, fields, regionSettings) { - var ret = {}; - - if (fields.iddPrefix !== undefined) { - // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. - this.processSubscriberNumber(number, fields, regionSettings); - ret.number = ""; - } else { - // found the idd prefix, so save it and cause the function to parse the next part - // of the number with the idd table - fields.iddPrefix = number.substring(0, 1); - - ret = { - number: number.substring(1), - table: 'idd' // shared subtable that parses the country code - }; - } - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - idd: function(number, currentChar, fields, regionSettings) { - var ret = {}; - - if (fields.iddPrefix !== undefined) { - // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. - this.processSubscriberNumber(number, fields, regionSettings); - ret.number = ""; - } else { - // found the idd prefix, so save it and cause the function to parse the next part - // of the number with the idd table - fields.iddPrefix = number.substring(0, currentChar+1); - - ret = { - number: number.substring(currentChar+1), - table: 'idd' // shared subtable that parses the country code - }; - } - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - country: function(number, currentChar, fields, regionSettings) { - var ret, cc; - - // found the country code of an IDD number, so save it and cause the function to - // parse the rest of the number with the regular table for this locale - fields.countryCode = number.substring(0, currentChar+1); - cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 - // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); - - ret = { - number: number.substring(currentChar+1), - countryCode: cc - }; - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - cic: function(number, currentChar, fields, regionSettings) { - return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - service: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - area: function(number, currentChar, fields, regionSettings) { - var ret, last, end, localLength; - - last = number.search(/[xwtp]/i); // last digit of the local number - localLength = (last > -1) ? last : number.length; - - if (regionSettings.plan.getFieldLength('areaCode') > 0) { - // fixed length - end = regionSettings.plan.getFieldLength('areaCode'); - if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { - end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code - localLength -= regionSettings.plan.getTrunkCode().length; - } - } else { - // variable length - // the setting is the negative of the length to add, so subtract to make it positive - end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); - } - - // substring() extracts the part of the string up to but not including the end character, - // so add one to compensate - if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { - if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || - fields.countryCode !== undefined || - localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { - // too long for a local number by itself, or a different final state already parsed out the trunk - // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number - fields.areaCode = number.substring(0, end); - if (number.length > end) { - this.processSubscriberNumber(number.substring(end), fields, regionSettings); - } - } else { - // shorter than the length needed for a local number, so just consider it a local number - this.processSubscriberNumber(number, fields, regionSettings); - } - } else { - fields.areaCode = number.substring(0, end); - if (number.length > end) { - this.processSubscriberNumber(number.substring(end), fields, regionSettings); - } - } - - // extensions are separated from the number by a dash in Germany - if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { - var dash = fields.subscriberNumber.indexOf("-"); - if (dash > -1) { - fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); - fields.extension = fields.subscriberNumber.substring(dash+1); - } - } - - ret = { - number: "" - }; - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - none: function(number, currentChar, fields, regionSettings) { - var ret; - - // this is a last resort function that is called when nothing is recognized. - // When this happens, just put the whole stripped number into the subscriber number - - if (number.length > 0) { - this.processSubscriberNumber(number, fields, regionSettings); - if (currentChar > 0 && currentChar < number.length) { - // if we were part-way through parsing, and we hit an invalid digit, - // indicate that the number could not be parsed properly - fields.invalid = true; - } - } - - ret = { - number:"" - }; - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - vsc: function(number, currentChar, fields, regionSettings) { - var ret, length, end; - - if (fields.vsc === undefined) { - length = regionSettings.plan.getFieldLength('vsc') || 0; - if (length !== undefined && length > 0) { - // fixed length - end = length; - } else { - // variable length - // the setting is the negative of the length to add, so subtract to make it positive - end = currentChar + 1 - length; - } - - // found a VSC code (ie. a "star code"), so save it and cause the function to - // parse the rest of the number with the same table for this locale - fields.vsc = number.substring(0, end); - number = (number.length > end) ? "^" + number.substring(end) : ""; - } else { - // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default - this.processSubscriberNumber(number, fields, regionSettings); - number = ""; - } - - // treat the rest of the number as if it were a completely new number - ret = { - number: number - }; - - return ret; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - cell: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - personal: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - emergency: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - premium: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - special: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - service2: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - service3: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - service4: function(number, currentChar, fields, regionSettings) { - return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - cic2: function(number, currentChar, fields, regionSettings) { - return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - cic3: function(number, currentChar, fields, regionSettings) { - return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - start: function(number, currentChar, fields, regionSettings) { - // don't do anything except transition to the next state - return { - number: number - }; - }, - /** - * @private - * @param {string} number phone number - * @param {number} currentChar currentChar to be parsed - * @param {Object} fields the fields that have been extracted so far - * @param {Object} regionSettings settings used to parse the rest of the number - */ - local: function(number, currentChar, fields, regionSettings) { - // in open dialling plans, we can tell that this number is a local subscriber number because it - // starts with a digit that indicates as such - this.processSubscriberNumber(number, fields, regionSettings); - return { - number: "" - }; - } + /** + * @private + * @param {string} number phone number + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + processSubscriberNumber: function(number, fields, regionSettings) { + var last; + + last = number.search(/[xwtp,;]/i); // last digit of the local number + + if (last > -1) { + if (last > 0) { + fields.subscriberNumber = number.substring(0, last); + } + // strip x's which are there to indicate a break between the local subscriber number and the extension, but + // are not themselves a dialable character + fields.extension = number.substring(last).replace('x', ''); + } else { + if (number.length) { + fields.subscriberNumber = number; + } + } + + if (regionSettings.plan.getFieldLength('maxLocalLength') && + fields.subscriberNumber && + fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { + fields.invalid = true; + } + }, + /** + * @private + * @param {string} fieldName + * @param {number} length length of phone number + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + * @param {boolean} noExtractTrunk + */ + processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { + var ret, end; + + if (length !== undefined && length > 0) { + // fixed length + end = length; + if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { + end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code + } + } else { + // variable length + // the setting is the negative of the length to add, so subtract to make it positive + end = currentChar + 1 - length; + } + + if (fields[fieldName] !== undefined) { + // we have a spurious recognition, because this number already contains that field! So, just put + // everything into the subscriberNumber as the default + this.processSubscriberNumber(number, fields, regionSettings); + } else { + fields[fieldName] = number.substring(0, end); + if (number.length > end) { + this.processSubscriberNumber(number.substring(end), fields, regionSettings); + } + } + + ret = { + number: "" + }; + + return ret; + }, + /** + * @private + * @param {string} fieldName + * @param {number} length length of phone number + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + processField: function(fieldName, length, number, currentChar, fields, regionSettings) { + var ret = {}, end; + + if (length !== undefined && length > 0) { + // fixed length + end = length; + if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { + end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code + } + } else { + // variable length + // the setting is the negative of the length to add, so subtract to make it positive + end = currentChar + 1 - length; + } + + if (fields[fieldName] !== undefined) { + // we have a spurious recognition, because this number already contains that field! So, just put + // everything into the subscriberNumber as the default + this.processSubscriberNumber(number, fields, regionSettings); + ret.number = ""; + } else { + fields[fieldName] = number.substring(0, end); + ret.number = (number.length > end) ? number.substring(end) : ""; + } + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + trunk: function(number, currentChar, fields, regionSettings) { + var ret, trunkLength; + + if (fields.trunkAccess !== undefined) { + // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. + this.processSubscriberNumber(number, fields, regionSettings); + number = ""; + } else { + trunkLength = regionSettings.plan.getTrunkCode().length; + fields.trunkAccess = number.substring(0, trunkLength); + number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; + } + + ret = { + number: number + }; + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + plus: function(number, currentChar, fields, regionSettings) { + var ret = {}; + + if (fields.iddPrefix !== undefined) { + // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. + this.processSubscriberNumber(number, fields, regionSettings); + ret.number = ""; + } else { + // found the idd prefix, so save it and cause the function to parse the next part + // of the number with the idd table + fields.iddPrefix = number.substring(0, 1); + + ret = { + number: number.substring(1), + table: 'idd' // shared subtable that parses the country code + }; + } + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + idd: function(number, currentChar, fields, regionSettings) { + var ret = {}; + + if (fields.iddPrefix !== undefined) { + // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. + this.processSubscriberNumber(number, fields, regionSettings); + ret.number = ""; + } else { + // found the idd prefix, so save it and cause the function to parse the next part + // of the number with the idd table + fields.iddPrefix = number.substring(0, currentChar+1); + + ret = { + number: number.substring(currentChar+1), + table: 'idd' // shared subtable that parses the country code + }; + } + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + country: function(number, currentChar, fields, regionSettings) { + var ret, cc; + + // found the country code of an IDD number, so save it and cause the function to + // parse the rest of the number with the regular table for this locale + fields.countryCode = number.substring(0, currentChar+1); + cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 + // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); + + ret = { + number: number.substring(currentChar+1), + countryCode: cc + }; + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + cic: function(number, currentChar, fields, regionSettings) { + return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + service: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + area: function(number, currentChar, fields, regionSettings) { + var ret, last, end, localLength; + + last = number.search(/[xwtp]/i); // last digit of the local number + localLength = (last > -1) ? last : number.length; + + if (regionSettings.plan.getFieldLength('areaCode') > 0) { + // fixed length + end = regionSettings.plan.getFieldLength('areaCode'); + if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { + end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code + localLength -= regionSettings.plan.getTrunkCode().length; + } + } else { + // variable length + // the setting is the negative of the length to add, so subtract to make it positive + end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); + } + + // substring() extracts the part of the string up to but not including the end character, + // so add one to compensate + if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { + if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || + fields.countryCode !== undefined || + localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { + // too long for a local number by itself, or a different final state already parsed out the trunk + // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number + fields.areaCode = number.substring(0, end); + if (number.length > end) { + this.processSubscriberNumber(number.substring(end), fields, regionSettings); + } + } else { + // shorter than the length needed for a local number, so just consider it a local number + this.processSubscriberNumber(number, fields, regionSettings); + } + } else { + fields.areaCode = number.substring(0, end); + if (number.length > end) { + this.processSubscriberNumber(number.substring(end), fields, regionSettings); + } + } + + // extensions are separated from the number by a dash in Germany + if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { + var dash = fields.subscriberNumber.indexOf("-"); + if (dash > -1) { + fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); + fields.extension = fields.subscriberNumber.substring(dash+1); + } + } + + ret = { + number: "" + }; + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + none: function(number, currentChar, fields, regionSettings) { + var ret; + + // this is a last resort function that is called when nothing is recognized. + // When this happens, just put the whole stripped number into the subscriber number + + if (number.length > 0) { + this.processSubscriberNumber(number, fields, regionSettings); + if (currentChar > 0 && currentChar < number.length) { + // if we were part-way through parsing, and we hit an invalid digit, + // indicate that the number could not be parsed properly + fields.invalid = true; + } + } + + ret = { + number:"" + }; + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + vsc: function(number, currentChar, fields, regionSettings) { + var ret, length, end; + + if (fields.vsc === undefined) { + length = regionSettings.plan.getFieldLength('vsc') || 0; + if (length !== undefined && length > 0) { + // fixed length + end = length; + } else { + // variable length + // the setting is the negative of the length to add, so subtract to make it positive + end = currentChar + 1 - length; + } + + // found a VSC code (ie. a "star code"), so save it and cause the function to + // parse the rest of the number with the same table for this locale + fields.vsc = number.substring(0, end); + number = (number.length > end) ? "^" + number.substring(end) : ""; + } else { + // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default + this.processSubscriberNumber(number, fields, regionSettings); + number = ""; + } + + // treat the rest of the number as if it were a completely new number + ret = { + number: number + }; + + return ret; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + cell: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + personal: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + emergency: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + premium: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + special: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + service2: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + service3: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + service4: function(number, currentChar, fields, regionSettings) { + return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + cic2: function(number, currentChar, fields, regionSettings) { + return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + cic3: function(number, currentChar, fields, regionSettings) { + return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + start: function(number, currentChar, fields, regionSettings) { + // don't do anything except transition to the next state + return { + number: number + }; + }, + /** + * @private + * @param {string} number phone number + * @param {number} currentChar currentChar to be parsed + * @param {Object} fields the fields that have been extracted so far + * @param {Object} regionSettings settings used to parse the rest of the number + */ + local: function(number, currentChar, fields, regionSettings) { + // in open dialling plans, we can tell that this number is a local subscriber number because it + // starts with a digit that indicates as such + this.processSubscriberNumber(number, fields, regionSettings); + return { + number: "" + }; + } }; // context-sensitive handler @@ -526,29 +526,29 @@ PhoneHandler.prototype = { * @constructor */ var CSStateHandler = function () { - return this; + return this; }; CSStateHandler.prototype = new PhoneHandler(); CSStateHandler.prototype.special = function (number, currentChar, fields, regionSettings) { - var ret; - - // found a special area code that is both a node and a leaf. In - // this state, we have found the leaf, so chop off the end - // character to make it a leaf. - if (number.charAt(0) === "0") { - fields.trunkAccess = number.charAt(0); - fields.areaCode = number.substring(1, currentChar); - } else { - fields.areaCode = number.substring(0, currentChar); - } - this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); - - ret = { - number: "" - }; - - return ret; + var ret; + + // found a special area code that is both a node and a leaf. In + // this state, we have found the leaf, so chop off the end + // character to make it a leaf. + if (number.charAt(0) === "0") { + fields.trunkAccess = number.charAt(0); + fields.areaCode = number.substring(1, currentChar); + } else { + fields.areaCode = number.substring(0, currentChar); + } + this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); + + ret = { + number: "" + }; + + return ret; }; /** @@ -558,44 +558,44 @@ CSStateHandler.prototype.special = function (number, currentChar, fields, region * @constructor */ var USStateHandler = function () { - return this; + return this; }; USStateHandler.prototype = new PhoneHandler(); USStateHandler.prototype.vsc = function (number, currentChar, fields, regionSettings) { - var ret; + var ret; - // found a VSC code (ie. a "star code") - fields.vsc = number; + // found a VSC code (ie. a "star code") + fields.vsc = number; - // treat the rest of the number as if it were a completely new number - ret = { - number: "" - }; + // treat the rest of the number as if it were a completely new number + ret = { + number: "" + }; - return ret; + return ret; }; /** * Creates a phone handler instance that is correct for the locale. Phone handlers are * used to handle parsing of the various fields in a phone number. - * + * * @private * @static * @return {PhoneHandler} the correct phone handler for the locale */ var PhoneHandlerFactory = function (locale, plan) { - if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { - return new CSStateHandler(); - } - var region = (locale && locale.getRegion()) || "ZZ"; - switch (region) { - case 'US': - return new USStateHandler(); - - default: - return new PhoneHandler(); - } + if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { + return new CSStateHandler(); + } + var region = (locale && locale.getRegion()) || "ZZ"; + switch (region) { + case 'US': + return new USStateHandler(); + + default: + return new PhoneHandler(); + } }; module.exports = PhoneHandlerFactory; \ No newline at end of file diff --git a/js/lib/PhoneLocale.js b/js/lib/PhoneLocale.js index 57a5ff326f..000263473a 100644 --- a/js/lib/PhoneLocale.js +++ b/js/lib/PhoneLocale.js @@ -1,6 +1,6 @@ /* * phoneloc.js - Represent a phone locale object. - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data phoneloc -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); @@ -29,73 +29,73 @@ var Locale = require("./Locale.js"); * related to phone number parsing. * * @param {Object} options Options that govern how this phone locale works - * + * * @private * @constructor * @extends Locale */ var PhoneLocale = function(options) { - var region, - mcc, - cc, - sync = true, - loadParams = {}, - locale; - - locale = (options && options.locale) || ilib.getLocale(); - - this.parent.call(this, locale); - - region = this.region; - - if (options) { - if (typeof(options.mcc) !== 'undefined') { - mcc = options.mcc; - } - - if (typeof(options.countryCode) !== 'undefined') { - cc = options.countryCode; - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - Utils.loadData({ - name: "phoneloc.json", - object: "PhoneLocale", - nonlocale: true, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (data) { - /** @type {{mcc2reg:Object.,cc2reg:Object.,reg2cc:Object.,area2reg:Object.}} */ - this.mappings = data; - - if (typeof(mcc) !== 'undefined') { - region = this.mappings.mcc2reg[mcc]; - } - - if (typeof(cc) !== 'undefined') { - region = this.mappings.cc2reg[cc]; - } - - if (!region) { - region = "XX"; - } - - this.region = this._normPhoneReg(region); - this._genSpec(); - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + var region, + mcc, + cc, + sync = true, + loadParams = {}, + locale; + + locale = (options && options.locale) || ilib.getLocale(); + + this.parent.call(this, locale); + + region = this.region; + + if (options) { + if (typeof(options.mcc) !== 'undefined') { + mcc = options.mcc; + } + + if (typeof(options.countryCode) !== 'undefined') { + cc = options.countryCode; + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + Utils.loadData({ + name: "phoneloc.json", + object: "PhoneLocale", + nonlocale: true, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (data) { + /** @type {{mcc2reg:Object.,cc2reg:Object.,reg2cc:Object.,area2reg:Object.}} */ + this.mappings = data; + + if (typeof(mcc) !== 'undefined') { + region = this.mappings.mcc2reg[mcc]; + } + + if (typeof(cc) !== 'undefined') { + region = this.mappings.cc2reg[cc]; + } + + if (!region) { + region = "XX"; + } + + this.region = this._normPhoneReg(region); + this._genSpec(); + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; PhoneLocale.prototype = new Locale(); @@ -112,10 +112,10 @@ PhoneLocale.prototype.constructor = PhoneLocale; */ PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { - if (!mcc) { - return undefined; - } - return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; + if (!mcc) { + return undefined; + } + return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; }; /** @@ -127,10 +127,10 @@ PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { * @return {string|undefined} the region code */ PhoneLocale.prototype._mapCCtoRegion = function(cc) { - if (!cc) { - return undefined; - } - return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; + if (!cc) { + return undefined; + } + return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; }; /** @@ -142,10 +142,10 @@ PhoneLocale.prototype._mapCCtoRegion = function(cc) { * @return {string|undefined} the country code */ PhoneLocale.prototype._mapRegiontoCC = function(region) { - if (!region) { - return undefined; - } - return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; + if (!region) { + return undefined; + } + return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; }; /** @@ -158,81 +158,81 @@ PhoneLocale.prototype._mapRegiontoCC = function(region) { * @return {string|undefined} the region code */ PhoneLocale.prototype._mapAreatoRegion = function(cc, area) { - if (!cc) { - return undefined; - } - if (cc in this.mappings.area2reg) { - return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; - } else { - return this.mappings.cc2reg[cc]; - } + if (!cc) { + return undefined; + } + if (cc in this.mappings.area2reg) { + return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; + } else { + return this.mappings.cc2reg[cc]; + } }; /** * Return the region that controls the dialing plan in the given * region. (ie. the "normalized phone region".) - * + * * @static * @package * @param {string} region the region code to normalize * @return {string} the normalized region code */ PhoneLocale.prototype._normPhoneReg = function(region) { - var norm; - - // Map all NANP regions to the right region, so that they get parsed using the - // correct state table - switch (region) { - case "US": // usa - case "CA": // canada - case "AG": // antigua and barbuda - case "BS": // bahamas - case "BB": // barbados - case "DM": // dominica - case "DO": // dominican republic - case "GD": // grenada - case "JM": // jamaica - case "KN": // st. kitts and nevis - case "LC": // st. lucia - case "VC": // st. vincent and the grenadines - case "TT": // trinidad and tobago - case "AI": // anguilla - case "BM": // bermuda - case "VG": // british virgin islands - case "KY": // cayman islands - case "MS": // montserrat - case "TC": // turks and caicos - case "AS": // American Samoa - case "VI": // Virgin Islands, U.S. - case "PR": // Puerto Rico - case "MP": // Northern Mariana Islands - case "T:": // East Timor - case "GU": // Guam - norm = "US"; - break; - - // these all use the Italian dialling plan - case "IT": // italy - case "SM": // san marino - case "VA": // vatican city - norm = "IT"; - break; - - // all the French dependencies are on the French dialling plan - case "FR": // france - case "GF": // french guiana - case "MQ": // martinique - case "GP": // guadeloupe, - case "BL": // saint barthélemy - case "MF": // saint martin - case "RE": // réunion, mayotte - norm = "FR"; - break; - default: - norm = region; - break; - } - return norm; + var norm; + + // Map all NANP regions to the right region, so that they get parsed using the + // correct state table + switch (region) { + case "US": // usa + case "CA": // canada + case "AG": // antigua and barbuda + case "BS": // bahamas + case "BB": // barbados + case "DM": // dominica + case "DO": // dominican republic + case "GD": // grenada + case "JM": // jamaica + case "KN": // st. kitts and nevis + case "LC": // st. lucia + case "VC": // st. vincent and the grenadines + case "TT": // trinidad and tobago + case "AI": // anguilla + case "BM": // bermuda + case "VG": // british virgin islands + case "KY": // cayman islands + case "MS": // montserrat + case "TC": // turks and caicos + case "AS": // American Samoa + case "VI": // Virgin Islands, U.S. + case "PR": // Puerto Rico + case "MP": // Northern Mariana Islands + case "T:": // East Timor + case "GU": // Guam + norm = "US"; + break; + + // these all use the Italian dialling plan + case "IT": // italy + case "SM": // san marino + case "VA": // vatican city + norm = "IT"; + break; + + // all the French dependencies are on the French dialling plan + case "FR": // france + case "GF": // french guiana + case "MQ": // martinique + case "GP": // guadeloupe, + case "BL": // saint barthélemy + case "MF": // saint martin + case "RE": // réunion, mayotte + norm = "FR"; + break; + default: + norm = region; + break; + } + return norm; }; module.exports = PhoneLocale; \ No newline at end of file diff --git a/js/lib/PhoneNumber.js b/js/lib/PhoneNumber.js index a1049e4f35..98374665ce 100644 --- a/js/lib/PhoneNumber.js +++ b/js/lib/PhoneNumber.js @@ -1,6 +1,6 @@ /* * PhoneNumber.js - Represent a phone number. - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data states idd mnc -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); var NumberingPlan = require("./NumberingPlan.js"); @@ -28,55 +28,55 @@ var PhoneHandlerFactory = require("./PhoneHandlerFactory.js"); /** * @class - * Create a new phone number instance that parses the phone number parameter for its + * Create a new phone number instance that parses the phone number parameter for its * constituent parts, and store them as separate fields in the returned object. - * + * * The options object may include any of these properties: - * + * *

      *
    • locale The locale with which to parse the number. This gives a clue as to which * numbering plan to use. - *
    • mcc The mobile carrier code (MCC) associated with the carrier that the phone is + *
    • mcc The mobile carrier code (MCC) associated with the carrier that the phone is * currently connected to, if known. This also can give a clue as to which numbering plan to * use - *
    • onLoad - a callback function to call when this instance is fully + *
    • onLoad - a callback function to call when this instance is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - *
    • sync - tell whether to load any missing locale data synchronously or + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * + * * This function is locale-sensitive, and will assume any number passed to it is * appropriate for the given locale. If the MCC is given, this method will assume * that numbers without an explicit country code have been dialled within the country * given by the MCC. This affects how things like area codes are parsed. If the MCC * is not given, this method will use the given locale to determine the country - * code. If the locale is not explicitly given either, then this function uses the + * code. If the locale is not explicitly given either, then this function uses the * region of current locale as the default.

    - * - * The input number may contain any formatting characters for the given locale. Each + * + * The input number may contain any formatting characters for the given locale. Each * field that is returned in the json object is a simple string of digits with * all formatting and whitespace characters removed.

    - * + * * The number is decomposed into its parts, regardless if the number - * contains formatting characters. If a particular part cannot be extracted from given + * contains formatting characters. If a particular part cannot be extracted from given * number, the field will not be returned as a field in the object. If no fields can be - * extracted from the number at all, then all digits found in the string will be - * returned in the subscriberNumber field. If the number parameter contains no + * extracted from the number at all, then all digits found in the string will be + * returned in the subscriberNumber field. If the number parameter contains no * digits, an empty object is returned.

    - * + * * This instance can contain any of the following fields after parsing is done: - * + * *

      *
    • vsc - if this number starts with a VSC (Vertical Service Code, or "star code"), this field will contain the star and the code together *
    • iddPrefix - the prefix for international direct dialing. This can either be in the form of a plus character or the IDD access code for the given locale @@ -91,28 +91,28 @@ var PhoneHandlerFactory = require("./PhoneHandlerFactory.js"); *
    • extension - in some countries, extensions are dialed directly without going through an operator or a voice prompt system. If the number includes an extension, it is given in this field. *
    • invalid - this property is added and set to true if the parser found that the number is invalid in the numbering plan for the country. This method will make its best effort at parsing, but any digits after the error will go into the subscriberNumber field *
    - * + * * The following rules determine how the number is parsed: - * + * *
      *
    1. If the number starts with a character that is alphabetic instead of numeric, do * not parse the number at all. There is a good chance that it is not really a phone number. * In this case, an empty instance will be returned. *
    2. If the phone number uses the plus notation or explicitly uses the international direct - * dialing prefix for the given locale, then the country code is identified in + * dialing prefix for the given locale, then the country code is identified in * the number. The rules of given locale are used to parse the IDD prefix, and then the rules * of the country in the prefix are used to parse the rest of the number. *
    3. If a country code is provided as an argument to the function call, use that country's - * parsing rules for the number. This is intended for programs like a Contacts application that - * know what the country is of the person that owns the phone number and can pass that on as + * parsing rules for the number. This is intended for programs like a Contacts application that + * know what the country is of the person that owns the phone number and can pass that on as * a hint. - *
    4. If the appropriate locale cannot be easily determined, default to using the rules + *
    5. If the appropriate locale cannot be easily determined, default to using the rules * for the current user's region. *
    - * + * * Example: parsing the number "+49 02101345345-78" will give the following properties in the * resulting phone number instance: - * + * *
      *      {
      *        iddPrefix: "+",
    @@ -122,13 +122,13 @@ var PhoneHandlerFactory = require("./PhoneHandlerFactory.js");
      *        extension: "78"
      *      }
      * 
    - * - * Note that in this example, because international direct dialing is explicitly used - * in the number, the part of this number after the IDD prefix and country code will be + * + * Note that in this example, because international direct dialing is explicitly used + * in the number, the part of this number after the IDD prefix and country code will be * parsed exactly the same way in all locales with German rules (country code 49). - * + * * Regions currently supported are: - * + * *
      *
    • NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations *
    • UK @@ -151,245 +151,245 @@ var PhoneHandlerFactory = require("./PhoneHandlerFactory.js"); *
    • Russia *
    • Brazil *
    - * + * * @constructor * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone * number instance to copy * @param {Object=} options options that guide the parser in parsing the number */ var PhoneNumber = function(number, options) { - var stateData, - regionSettings; - - this.sync = true; - this.loadParams = {}; - - - if (options) { - if (typeof(options.sync) === 'boolean') { - this.sync = options.sync; - } - - if (options.loadParams) { - this.loadParams = options.loadParams; - } - - if (typeof(options.onLoad) === 'function') { - /** - * @private - * @type {function(PhoneNumber)} - */ - this.onLoad = options.onLoad; - } - } else { - options = {sync: true}; - } - - if (!number || (typeof number === "string" && number.length === 0)) { + var stateData, + regionSettings; + + this.sync = true; + this.loadParams = {}; + + + if (options) { + if (typeof(options.sync) === 'boolean') { + this.sync = options.sync; + } + + if (options.loadParams) { + this.loadParams = options.loadParams; + } + + if (typeof(options.onLoad) === 'function') { + /** + * @private + * @type {function(PhoneNumber)} + */ + this.onLoad = options.onLoad; + } + } else { + options = {sync: true}; + } + + if (!number || (typeof number === "string" && number.length === 0)) { if (typeof(options.onLoad) === 'function') { options.onLoad(undefined); } - return this; - } - - if (typeof number === "object") { - /** - * The vertical service code. These are codes that typically - * start with a star or hash, like "*69" for "dial back the - * last number that called me". - * @type {string|undefined} - */ - this.vsc = number.vsc; - - /** - * The international direct dialing prefix. This is always - * followed by the country code. - * @type {string} - */ - this.iddPrefix = number.iddPrefix; - - /** - * The unique IDD country code for the country where the - * phone number is serviced. - * @type {string|undefined} - */ - this.countryCode = number.countryCode; - - /** - * The digits required to access the trunk. - * @type {string|undefined} - */ - this.trunkAccess = number.trunkAccess; - - /** - * The carrier identification code used to identify - * alternate long distance or international carriers. - * @type {string|undefined} - */ - this.cic = number.cic; - - /** - * Identifies an emergency number that is typically - * short, such as "911" in North America or "112" in - * many other places in the world. - * @type {string|undefined} - */ - this.emergency = number.emergency; - - /** - * The prefix of the subscriber number that indicates - * that this is the number of a mobile phone. - * @type {string|undefined} - */ - this.mobilePrefix = number.mobilePrefix; - - /** - * The prefix that identifies this number as commercial - * service number. - * @type {string|undefined} - */ - this.serviceCode = number.serviceCode; - - /** - * The area code prefix of a land line number. - * @type {string|undefined} - */ - this.areaCode = number.areaCode; - - /** - * The unique number associated with the subscriber - * of this phone. - * @type {string|undefined} - */ - this.subscriberNumber = number.subscriberNumber; - - /** - * The direct dial extension number. - * @type {string|undefined} - */ - this.extension = number.extension; - - /** - * @private - * @type {boolean} - */ - this.invalid = number.invalid; - - if (number.plan && number.locale) { - /** - * @private - * @type {NumberingPlan} - */ - this.plan = number.plan; - - /** - * @private - * @type {PhoneLocale} - */ - this.locale = number.locale; - - /** - * @private - * @type {NumberingPlan} - */ - this.destinationPlan = number.destinationPlan; - - /** - * @private - * @type {PhoneLocale} - */ - this.destinationLocale = number.destinationLocale; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - return; - } - } - - new PhoneLocale({ - locale: options && options.locale, - mcc: options && options.mcc, - sync: this.sync, - loadParams: this.loadParams, - onLoad: ilib.bind(this, function(loc) { - this.locale = this.destinationLocale = loc; - new NumberingPlan({ - locale: this.locale, - sync: this.sync, - loadParms: this.loadParams, - onLoad: ilib.bind(this, function (plan) { - this.plan = this.destinationPlan = plan; - - if (typeof number === "object") { - // the copy constructor code above did not find the locale - // or plan before, but now they are loaded, so we can return - // already without going further - if (typeof(options.onLoad) === "function") { - options.onLoad(this); - } - return; - } - Utils.loadData({ - name: "states.json", - object: "PhoneNumber", - locale: this.locale, - sync: this.sync, - loadParams: JSUtils.merge(this.loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (stdata) { - if (!stdata) { - stdata = PhoneNumber._defaultStates; - } - - stateData = stdata; - - regionSettings = { - stateData: stateData, - plan: plan, - handler: PhoneHandlerFactory(this.locale, plan) - }; - - // use ^ to indicate the beginning of the number, because certain things only match at the beginning - number = "^" + number.replace(/\^/g, ''); - number = PhoneNumber._stripFormatting(number); - - this._parseNumber(number, regionSettings, options); - }) - }); - }) - }); - }) - }); + return this; + } + + if (typeof number === "object") { + /** + * The vertical service code. These are codes that typically + * start with a star or hash, like "*69" for "dial back the + * last number that called me". + * @type {string|undefined} + */ + this.vsc = number.vsc; + + /** + * The international direct dialing prefix. This is always + * followed by the country code. + * @type {string} + */ + this.iddPrefix = number.iddPrefix; + + /** + * The unique IDD country code for the country where the + * phone number is serviced. + * @type {string|undefined} + */ + this.countryCode = number.countryCode; + + /** + * The digits required to access the trunk. + * @type {string|undefined} + */ + this.trunkAccess = number.trunkAccess; + + /** + * The carrier identification code used to identify + * alternate long distance or international carriers. + * @type {string|undefined} + */ + this.cic = number.cic; + + /** + * Identifies an emergency number that is typically + * short, such as "911" in North America or "112" in + * many other places in the world. + * @type {string|undefined} + */ + this.emergency = number.emergency; + + /** + * The prefix of the subscriber number that indicates + * that this is the number of a mobile phone. + * @type {string|undefined} + */ + this.mobilePrefix = number.mobilePrefix; + + /** + * The prefix that identifies this number as commercial + * service number. + * @type {string|undefined} + */ + this.serviceCode = number.serviceCode; + + /** + * The area code prefix of a land line number. + * @type {string|undefined} + */ + this.areaCode = number.areaCode; + + /** + * The unique number associated with the subscriber + * of this phone. + * @type {string|undefined} + */ + this.subscriberNumber = number.subscriberNumber; + + /** + * The direct dial extension number. + * @type {string|undefined} + */ + this.extension = number.extension; + + /** + * @private + * @type {boolean} + */ + this.invalid = number.invalid; + + if (number.plan && number.locale) { + /** + * @private + * @type {NumberingPlan} + */ + this.plan = number.plan; + + /** + * @private + * @type {PhoneLocale} + */ + this.locale = number.locale; + + /** + * @private + * @type {NumberingPlan} + */ + this.destinationPlan = number.destinationPlan; + + /** + * @private + * @type {PhoneLocale} + */ + this.destinationLocale = number.destinationLocale; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + return; + } + } + + new PhoneLocale({ + locale: options && options.locale, + mcc: options && options.mcc, + sync: this.sync, + loadParams: this.loadParams, + onLoad: ilib.bind(this, function(loc) { + this.locale = this.destinationLocale = loc; + new NumberingPlan({ + locale: this.locale, + sync: this.sync, + loadParms: this.loadParams, + onLoad: ilib.bind(this, function (plan) { + this.plan = this.destinationPlan = plan; + + if (typeof number === "object") { + // the copy constructor code above did not find the locale + // or plan before, but now they are loaded, so we can return + // already without going further + if (typeof(options.onLoad) === "function") { + options.onLoad(this); + } + return; + } + Utils.loadData({ + name: "states.json", + object: "PhoneNumber", + locale: this.locale, + sync: this.sync, + loadParams: JSUtils.merge(this.loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (stdata) { + if (!stdata) { + stdata = PhoneNumber._defaultStates; + } + + stateData = stdata; + + regionSettings = { + stateData: stateData, + plan: plan, + handler: PhoneHandlerFactory(this.locale, plan) + }; + + // use ^ to indicate the beginning of the number, because certain things only match at the beginning + number = "^" + number.replace(/\^/g, ''); + number = PhoneNumber._stripFormatting(number); + + this._parseNumber(number, regionSettings, options); + }) + }); + }) + }); + }) + }); }; /** * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts: - * + * *
      - *
    1. mcc - Mobile Country Code, which identifies the country where the phone is currently receiving + *
    2. mcc - Mobile Country Code, which identifies the country where the phone is currently receiving * service. - *
    3. mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone - *
    4. msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on + *
    5. mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone + *
    6. msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on * the network, which usually maps to an account/subscriber in the carrier's database. *
    - * + * * Because this function may need to load data to identify the above parts, you can pass an options * object that controls how the data is loaded. The options may contain any of the following properties: * *
      - *
    • onLoad - a callback function to call when the parsing is done. When the onLoad option is given, + *
    • onLoad - a callback function to call when the parsing is done. When the onLoad option is given, * this method will attempt to load the locale data using the ilib loader callback. When it is done * (even if the data is already preassembled), the onLoad function is called with the parsing results - * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or + * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or * asynchronous loading or a mix of the above. - *
    • sync - tell whether to load any missing locale data synchronously or asynchronously. If this - * option is given as "false", then the "onLoad" callback must be given, as the results returned from + *
    • sync - tell whether to load any missing locale data synchronously or asynchronously. If this + * option is given as "false", then the "onLoad" callback must be given, as the results returned from * this constructor will not be usable for a while. - *
    • loadParams - an object containing parameters to pass to the loader callback function - * when locale data is missing. The parameters are not interpretted or modified in any way. They are + *
    • loadParams - an object containing parameters to pass to the loader callback function + * when locale data is missing. The parameters are not interpretted or modified in any way. They are * simply passed along. The object may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    @@ -401,51 +401,51 @@ var PhoneNumber = function(number, options) { * is loaded synchronously, or undefined if asynchronous */ PhoneNumber.parseImsi = function(imsi, options) { - var sync = true, - loadParams = {}, - fields = {}; - - if (!imsi) { - if (options && typeof(options.onLoad) === 'function') { + var sync = true, + loadParams = {}, + fields = {}; + + if (!imsi) { + if (options && typeof(options.onLoad) === 'function') { options.onLoad(undefined); } - return undefined; - } - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - if (ilib.data.mnc) { - fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(fields); - } - } else { - Utils.loadData({ - name: "mnc.json", - object: "PhoneNumber", - nonlocale: true, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function(data) { - ilib.data.mnc = data; - fields = PhoneNumber._parseImsi(data, imsi); - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(fields); - } - }) - }); - } - return fields; + return undefined; + } + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + if (ilib.data.mnc) { + fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(fields); + } + } else { + Utils.loadData({ + name: "mnc.json", + object: "PhoneNumber", + nonlocale: true, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function(data) { + ilib.data.mnc = data; + fields = PhoneNumber._parseImsi(data, imsi); + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(fields); + } + }) + }); + } + return fields; }; @@ -454,105 +454,105 @@ PhoneNumber.parseImsi = function(imsi, options) { * @protected */ PhoneNumber._parseImsi = function(data, imsi) { - var ch, - i, - currentState, - end, - handlerMethod, - newState, - fields = {}, - lastLeaf, - consumed = 0; - - currentState = data; - if (!currentState) { - // can't parse anything - return undefined; - } - - i = 0; - while (i < imsi.length) { - ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); - // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); - if (ch >= 0) { - newState = currentState.s && currentState.s[ch]; - - if (typeof(newState) === 'object') { - if (typeof(newState.l) !== 'undefined') { - // save for latter if needed - lastLeaf = newState; - consumed = i; - } - // console.info("recognized digit " + ch + " continuing..."); - // recognized digit, so continue parsing - currentState = newState; - i++; - } else { - if ((typeof(newState) === 'undefined' || newState === 0 || - (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && - lastLeaf) { - // this is possibly a look-ahead and it didn't work... - // so fall back to the last leaf and use that as the - // final state - newState = lastLeaf; - i = consumed; - } - - if ((typeof(newState) === 'number' && newState) || - (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { - // final state - var stateNumber = typeof(newState) === 'number' ? newState : newState.l; - handlerMethod = PhoneNumber._states[stateNumber]; - - // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); - - // deal with syntactic ambiguity by using the "special" end state instead of "area" - if ( handlerMethod === "area" ) { - end = i+1; - } else { - // unrecognized imsi, so just assume the mnc is 3 digits - end = 6; - } - - fields.mcc = imsi.substring(0,3); - fields.mnc = imsi.substring(3,end); - fields.msin = imsi.substring(end); - - return fields; - } else { - // parse error - if (imsi.length >= 6) { - fields.mcc = imsi.substring(0,3); - fields.mnc = imsi.substring(3,6); - fields.msin = imsi.substring(6); - } - return fields; - } - } - } else if (ch === -1) { - // non-transition character, continue parsing in the same state - i++; - } else { - // should not happen - // console.info("skipping character " + ch); - // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. - i++; - } - } - - if (i >= imsi.length && imsi.length >= 6) { - // we reached the end of the imsi, but did not finish recognizing anything. - // Default to last resort and assume 3 digit mnc - fields.mcc = imsi.substring(0,3); - fields.mnc = imsi.substring(3,6); - fields.msin = imsi.substring(6); - } else { - // unknown or not enough characters for a real imsi - fields = undefined; - } - - // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); - return fields; + var ch, + i, + currentState, + end, + handlerMethod, + newState, + fields = {}, + lastLeaf, + consumed = 0; + + currentState = data; + if (!currentState) { + // can't parse anything + return undefined; + } + + i = 0; + while (i < imsi.length) { + ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); + // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); + if (ch >= 0) { + newState = currentState.s && currentState.s[ch]; + + if (typeof(newState) === 'object') { + if (typeof(newState.l) !== 'undefined') { + // save for latter if needed + lastLeaf = newState; + consumed = i; + } + // console.info("recognized digit " + ch + " continuing..."); + // recognized digit, so continue parsing + currentState = newState; + i++; + } else { + if ((typeof(newState) === 'undefined' || newState === 0 || + (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && + lastLeaf) { + // this is possibly a look-ahead and it didn't work... + // so fall back to the last leaf and use that as the + // final state + newState = lastLeaf; + i = consumed; + } + + if ((typeof(newState) === 'number' && newState) || + (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { + // final state + var stateNumber = typeof(newState) === 'number' ? newState : newState.l; + handlerMethod = PhoneNumber._states[stateNumber]; + + // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); + + // deal with syntactic ambiguity by using the "special" end state instead of "area" + if ( handlerMethod === "area" ) { + end = i+1; + } else { + // unrecognized imsi, so just assume the mnc is 3 digits + end = 6; + } + + fields.mcc = imsi.substring(0,3); + fields.mnc = imsi.substring(3,end); + fields.msin = imsi.substring(end); + + return fields; + } else { + // parse error + if (imsi.length >= 6) { + fields.mcc = imsi.substring(0,3); + fields.mnc = imsi.substring(3,6); + fields.msin = imsi.substring(6); + } + return fields; + } + } + } else if (ch === -1) { + // non-transition character, continue parsing in the same state + i++; + } else { + // should not happen + // console.info("skipping character " + ch); + // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. + i++; + } + } + + if (i >= imsi.length && imsi.length >= 6) { + // we reached the end of the imsi, but did not finish recognizing anything. + // Default to last resort and assume 3 digit mnc + fields.mcc = imsi.substring(0,3); + fields.mnc = imsi.substring(3,6); + fields.msin = imsi.substring(6); + } else { + // unknown or not enough characters for a real imsi + fields = undefined; + } + + // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); + return fields; }; /** @@ -560,15 +560,15 @@ PhoneNumber._parseImsi = function(data, imsi) { * @private */ PhoneNumber._stripFormatting = function(str) { - var ret = ""; - var i; - - for (i = 0; i < str.length; i++) { - if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { - ret += str.charAt(i); - } - } - return ret; + var ret = ""; + var i; + + for (i = 0; i < str.length; i++) { + if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { + ret += str.charAt(i); + } + } + return ret; }; /** @@ -576,83 +576,83 @@ PhoneNumber._stripFormatting = function(str) { * @protected */ PhoneNumber._getCharacterCode = function(ch) { - if (ch >= '0' && ch <= '9') { - return ch - '0'; - } - switch (ch) { - case '+': - return 10; - case '*': - return 11; - case '#': - return 12; - case '^': - return 13; - case 'p': // pause chars - case 'P': - case 't': - case 'T': - case 'w': - case 'W': - return -1; - case 'x': - case 'X': - case ',': - case ';': // extension char - return -1; - } - return -2; + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + switch (ch) { + case '+': + return 10; + case '*': + return 11; + case '#': + return 12; + case '^': + return 13; + case 'p': // pause chars + case 'P': + case 't': + case 'T': + case 'w': + case 'W': + return -1; + case 'x': + case 'X': + case ',': + case ';': // extension char + return -1; + } + return -2; }; /** * @private */ PhoneNumber._states = [ - "none", - "unknown", - "plus", - "idd", - "cic", - "service", - "cell", - "area", - "vsc", - "country", - "personal", - "special", - "trunk", - "premium", - "emergency", - "service2", - "service3", - "service4", - "cic2", - "cic3", - "start", - "local" + "none", + "unknown", + "plus", + "idd", + "cic", + "service", + "cell", + "area", + "vsc", + "country", + "personal", + "special", + "trunk", + "premium", + "emergency", + "service2", + "service3", + "service4", + "cic2", + "cic3", + "start", + "local" ]; /** * @private */ PhoneNumber._fieldOrder = [ - "vsc", - "iddPrefix", - "countryCode", - "trunkAccess", - "cic", - "emergency", - "mobilePrefix", - "serviceCode", - "areaCode", - "subscriberNumber", - "extension" + "vsc", + "iddPrefix", + "countryCode", + "trunkAccess", + "cic", + "emergency", + "mobilePrefix", + "serviceCode", + "areaCode", + "subscriberNumber", + "extension" ]; PhoneNumber._defaultStates = { - "s": [ + "s": [ 0, - 21, // 1 -> local + 21, // 1 -> local 21, // 2 -> local 21, // 3 -> local 21, // 4 -> local @@ -662,946 +662,946 @@ PhoneNumber._defaultStates = { 21, // 8 -> local 21, // 9 -> local 0,0,0, - { // ^ - "s": [ - { // ^0 - "s": [3], // ^00 -> idd - "l": 12 // ^0 -> trunk - }, - 21, // ^1 -> local - 21, // ^2 -> local - 21, // ^3 -> local - 21, // ^4 -> local - 21, // ^5 -> local - 21, // ^6 -> local - 21, // ^7 -> local - 21, // ^8 -> local - 21, // ^9 -> local - 2 // ^+ -> plus - ] - } - ] + { // ^ + "s": [ + { // ^0 + "s": [3], // ^00 -> idd + "l": 12 // ^0 -> trunk + }, + 21, // ^1 -> local + 21, // ^2 -> local + 21, // ^3 -> local + 21, // ^4 -> local + 21, // ^5 -> local + 21, // ^6 -> local + 21, // ^7 -> local + 21, // ^8 -> local + 21, // ^9 -> local + 2 // ^+ -> plus + ] + } + ] }; PhoneNumber.prototype = { - /** - * @protected - * @param {string} number - * @param {Object} regionData - * @param {Object} options - * @param {string} countryCode - */ - _parseOtherCountry: function(number, regionData, options, countryCode) { - new PhoneLocale({ - locale: this.locale, - countryCode: countryCode, - sync: this.sync, - loadParms: this.loadParams, - onLoad: ilib.bind(this, function (loc) { - /* - * this.locale is the locale where this number is being parsed, - * and is used to parse the IDD prefix, if any, and this.destinationLocale is - * the locale of the rest of this number after the IDD prefix. - */ - /** @type {PhoneLocale} */ - this.destinationLocale = loc; - - Utils.loadData({ - name: "states.json", - object: "PhoneNumber", - locale: this.destinationLocale, - sync: this.sync, - loadParams: JSUtils.merge(this.loadParams, { - returnOne: true - }), - callback: ilib.bind(this, function (stateData) { - if (!stateData) { - stateData = PhoneNumber._defaultStates; - } - - new NumberingPlan({ - locale: this.destinationLocale, - sync: this.sync, - loadParms: this.loadParams, - onLoad: ilib.bind(this, function (plan) { - /* - * this.plan is the plan where this number is being parsed, - * and is used to parse the IDD prefix, if any, and this.destinationPlan is - * the plan of the rest of this number after the IDD prefix in the - * destination locale. - */ - /** @type {NumberingPlan} */ - this.destinationPlan = plan; - - var regionSettings = { - stateData: stateData, - plan: plan, - handler: PhoneHandlerFactory(this.destinationLocale, plan) - }; - - // for plans that do not skip the trunk code when dialing from - // abroad, we need to treat the number from here on in as if it - // were parsing a local number from scratch. That way, the parser - // does not get confused between parts of the number at the - // beginning of the number, and parts in the middle. - if (!plan.getSkipTrunk()) { - number = '^' + number; - } - - // recursively call the parser with the new states data - // to finish the parsing - this._parseNumber(number, regionSettings, options); - }) - }); - }) - }); - }) - }); - }, - - /** - * @protected - * @param {string} number - * @param {Object} regionData - * @param {Object} options - */ - _parseNumber: function(number, regionData, options) { - var i, ch, - regionSettings, - newState, - dot, - handlerMethod, - result, - currentState = regionData.stateData, - lastLeaf = undefined, - consumed = 0; - - regionSettings = regionData; - dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java - - i = 0; - while (i < number.length) { - ch = PhoneNumber._getCharacterCode(number.charAt(i)); - if (ch >= 0) { - // newState = stateData.states[state][ch]; - newState = currentState.s && currentState.s[ch]; - - if (!newState && currentState.s && currentState.s[dot]) { - newState = currentState.s[dot]; - } - - if (typeof(newState) === 'object' && i+1 < number.length) { - if (typeof(newState.l) !== 'undefined') { - // this is a leaf node, so save that for later if needed - lastLeaf = newState; - consumed = i; - } - // console.info("recognized digit " + ch + " continuing..."); - // recognized digit, so continue parsing - currentState = newState; - i++; - } else { - if ((typeof(newState) === 'undefined' || newState === 0 || - (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && - lastLeaf) { - // this is possibly a look-ahead and it didn't work... - // so fall back to the last leaf and use that as the - // final state - newState = lastLeaf; - i = consumed; - consumed = 0; - lastLeaf = undefined; - } - - if ((typeof(newState) === 'number' && newState) || - (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { - // final state - var stateNumber = typeof(newState) === 'number' ? newState : newState.l; - handlerMethod = PhoneNumber._states[stateNumber]; - - if (number.charAt(0) === '^') { - result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); - } else { - result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); - } - - // reparse whatever is left - number = result.number; - i = consumed = 0; - lastLeaf = undefined; - - //console.log("reparsing with new number: " + number); - currentState = regionSettings.stateData; - // if the handler requested a special sub-table, use it for this round of parsing, - // otherwise, set it back to the regular table to continue parsing - - if (result.countryCode !== undefined) { - this._parseOtherCountry(number, regionData, options, result.countryCode); - // don't process any further -- let the work be done in the onLoad callbacks - return; - } else if (result.table !== undefined) { - Utils.loadData({ - name: result.table + ".json", - object: "PhoneNumber", - nonlocale: true, - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (data) { - if (!data) { - data = PhoneNumber._defaultStates; - } - - regionSettings = { - stateData: data, - plan: regionSettings.plan, - handler: regionSettings.handler - }; - - // recursively call the parser with the new states data - // to finish the parsing - this._parseNumber(number, regionSettings, options); - }) - }); - // don't process any further -- let the work be done in the onLoad callbacks - return; - } else if (result.skipTrunk !== undefined) { - ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); - currentState = currentState.s && currentState.s[ch]; - } - } else { - handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; - // failed parse. Either no last leaf to fall back to, or there was an explicit - // zero in the table. Put everything else in the subscriberNumber field as the - // default place - if (number.charAt(0) === '^') { - result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); - } else { - result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); - } - break; - } - } - } else if (ch === -1) { - // non-transition character, continue parsing in the same state - i++; - } else { - // should not happen - // console.info("skipping character " + ch); - // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. - i++; - } - } - if (i >= number.length && currentState !== regionData.stateData) { - handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; - // we reached the end of the phone number, but did not finish recognizing anything. - // Default to last resort and put everything that is left into the subscriber number - //console.log("Reached end of number before parsing was complete. Using handler for method none.") - if (number.charAt(0) === '^') { - result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); - } else { - result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); - } - } - - // let the caller know we are done parsing - if (this.onLoad) { - this.onLoad(this); - } - }, - /** - * @protected - */ - _getPrefix: function() { - return this.areaCode || this.serviceCode || this.mobilePrefix || ""; - }, - - /** - * @protected - */ - _hasPrefix: function() { - return (this._getPrefix() !== ""); - }, - - /** - * Exclusive or -- return true, if one is defined and the other isn't - * @protected - */ - _xor : function(left, right) { - if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { - return false; - } else { - return true; - } - }, - - /** - * return a version of the phone number that contains only the dialable digits in the correct order - * @protected - */ - _join: function () { - var fieldName, formatted = ""; - - try { - for (var field in PhoneNumber._fieldOrder) { - if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { - fieldName = PhoneNumber._fieldOrder[field]; - // console.info("normalize: formatting field " + fieldName); - if (this[fieldName] !== undefined) { - formatted += this[fieldName]; - } - } - } - } catch ( e ) { - //console.warn("caught exception in _join: " + e); - throw e; - } - return formatted; - }, - - /** - * This routine will compare the two phone numbers in an locale-sensitive - * manner to see if they possibly reference the same phone number.

    - * - * In many places, - * there are multiple ways to reach the same phone number. In North America for - * example, you might have a number with the trunk access code of "1" and another - * without, and they reference the exact same phone number. This is considered a - * strong match. For a different pair of numbers, one may be a local number and - * the other a full phone number with area code, which may reference the same - * phone number if the local number happens to be located in that area code. - * However, you cannot say for sure if it is in that area code, so it will - * be considered a somewhat weaker match.

    - * - * Similarly, in other countries, there are sometimes different ways of - * reaching the same destination, and the way that numbers - * match depends on the locale.

    - * - * The various phone number fields are handled differently for matches. There - * are various fields that do not need to match at all. For example, you may - * type equally enter "00" or "+" into your phone to start international direct - * dialling, so the iddPrefix field does not need to match at all.

    - * - * Typically, fields that require matches need to match exactly if both sides have a value - * for that field. If both sides specify a value and those values differ, that is - * a strong non-match. If one side does not have a value and the other does, that - * causes a partial match, because the number with the missing field may possibly - * have an implied value that matches the other number. For example, the numbers - * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" - * might possibly have the same 650 area code as the first number, and might possibly - * not. If both side do not specify a value for a particular field, that field is - * considered matching.

    - * - * The values of following fields are ignored when performing matches: - * - *

      - *
    • vsc - *
    • iddPrefix - *
    • cic - *
    • trunkAccess - *
    - * - * The values of the following fields matter if they do not match: - * - *
      - *
    • countryCode - A difference causes a moderately strong problem except for - * certain countries where there is a way to access the same subscriber via IDD - * and via intranetwork dialling - *
    • mobilePrefix - A difference causes a possible non-match - *
    • serviceCode - A difference causes a possible non-match - *
    • areaCode - A difference causes a possible non-match - *
    • subscriberNumber - A difference causes a very strong non-match - *
    • extension - A difference causes a minor non-match - *
    - * - * @param {string|PhoneNumber} other other phone number to compare this one to - * @return {number} non-negative integer describing the percentage quality of the - * match. 100 means a very strong match (100%), and lower numbers are less and - * less strong, down to 0 meaning not at all a match. - */ - compare: function (other) { - var match = 100, - FRdepartments = {"590":1, "594":1, "596":1, "262":1}, - ITcountries = {"378":1, "379":1}, - thisPrefix, - otherPrefix, - currentCountryCode = 0; - - if (typeof this.locale.region === "string") { - currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); - } - - // subscriber number must be present and must match - if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { - return 0; - } - - // extension must match if it is present - if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { - return 0; - } - - if (this._xor(this.countryCode, other.countryCode)) { - // if one doesn't have a country code, give it some demerit points, but if the - // one that has the country code has something other than the current country - // add even more. Ignore the special cases where you can dial the same number internationally or via - // the local numbering system - switch (this.locale.getRegion()) { - case 'FR': - if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { - if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { - match -= 100; - } - } else { - match -= 16; - } - break; - case 'IT': - if (this.countryCode in ITcountries || other.countryCode in ITcountries) { - if (this.areaCode !== other.areaCode) { - match -= 100; - } - } else { - match -= 16; - } - break; - default: - match -= 16; - if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || - (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { - match -= 16; - } - } - } else if (this.countryCode !== other.countryCode) { - // ignore the special cases where you can dial the same number internationally or via - // the local numbering system - if (other.countryCode === '33' || this.countryCode === '33') { - // france - if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { - if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { - match -= 100; - } - } else { - match -= 100; - } - } else if (this.countryCode === '39' || other.countryCode === '39') { - // italy - if (this.countryCode in ITcountries || other.countryCode in ITcountries) { - if (this.areaCode !== other.areaCode) { - match -= 100; - } - } else { - match -= 100; - } - } else { - match -= 100; - } - } - - if (this._xor(this.serviceCode, other.serviceCode)) { - match -= 20; - } else if (this.serviceCode !== other.serviceCode) { - match -= 100; - } - - if (this._xor(this.mobilePrefix, other.mobilePrefix)) { - match -= 20; - } else if (this.mobilePrefix !== other.mobilePrefix) { - match -= 100; - } - - if (this._xor(this.areaCode, other.areaCode)) { - // one has an area code, the other doesn't, so dock some points. It could be a match if the local - // number in the one number has the same implied area code as the explicit area code in the other number. - match -= 12; - } else if (this.areaCode !== other.areaCode) { - match -= 100; - } - - thisPrefix = this._getPrefix(); - otherPrefix = other._getPrefix(); - - if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { - match -= 100; - } - - // make sure we are between 0 and 100 - if (match < 0) { - match = 0; - } else if (match > 100) { - match = 100; - } - - return match; - }, - - /** - * Determine whether or not the other phone number is exactly equal to the current one.

    - * - * The difference between the compare method and the equals method is that the compare - * method compares normalized numbers with each other and returns the degree of match, - * whereas the equals operator returns true iff the two numbers contain the same fields - * and the fields are exactly the same. Functions and other non-phone number properties - * are not compared. - * @param {string|PhoneNumber} other another phone number to compare to this one - * @return {boolean} true if the numbers are the same, false otherwise - */ - equals: function equals(other) { - if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { - return false; - } - - var _this = this; - return PhoneNumber._fieldOrder.every(function(field) { - return _this[field] === other[field]; - }); - }, - - /** - * @private - * @param {{ - * mcc:string, - * defaultAreaCode:string, - * country:string, - * networkType:string, - * assistedDialing:boolean, - * sms:boolean, - * manualDialing:boolean - * }} options an object containing options to help in normalizing. - * @param {PhoneNumber} norm - * @param {PhoneLocale} homeLocale - * @param {PhoneLocale} currentLocale - * @param {NumberingPlan} currentPlan - * @param {PhoneLocale} destinationLocale - * @param {NumberingPlan} destinationPlan - * @param {boolean} sync - * @param {Object|undefined} loadParams - */ - _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { - var formatted = ""; - - if (!norm.invalid && options && options.assistedDialing) { - // don't normalize things that don't have subscriber numbers. Also, don't normalize - // manually dialed local numbers. Do normalize local numbers in contact entries. - if (norm.subscriberNumber && - (!options.manualDialing || - norm.iddPrefix || - norm.countryCode || - norm.trunkAccess)) { - // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); - if (currentLocale.getRegion() !== destinationLocale.getRegion()) { - // we are currently calling internationally - if (!norm._hasPrefix() && - options.defaultAreaCode && - destinationLocale.getRegion() === homeLocale.getRegion() && - (!destinationPlan.getFieldLength("minLocalLength") || - norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { - // area code is required when dialling from international, but only add it if we are dialing - // to our home area. Otherwise, the default area code is not valid! - norm.areaCode = options.defaultAreaCode; - if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { - // some phone systems require the trunk access code, even when dialling from international - norm.trunkAccess = destinationPlan.getTrunkCode(); - } - } - - if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { - // on some phone systems, the trunk access code is dropped when dialling from international - delete norm.trunkAccess; - } - - // make sure to get the country code for the destination region, not the current region! - if (options.sms) { - if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { - if (destinationLocale.getRegion() !== "US") { - norm.iddPrefix = "011"; // non-standard code to make it go through the US first - norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); - } else if (options.networkType === "cdma") { - delete norm.iddPrefix; - delete norm.countryCode; - if (norm.areaCode) { - norm.trunkAccess = "1"; - } - } else if (norm.areaCode) { - norm.iddPrefix = "+"; - norm.countryCode = "1"; - delete norm.trunkAccess; - } - } else { - norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; - norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); - } - } else if (norm._hasPrefix() && !norm.countryCode) { - norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); - } - - if (norm.countryCode && !options.sms) { - // for CDMA, make sure to get the international dialling access code for the current region, not the destination region - // all umts carriers support plus dialing - norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; - } - } else { - // console.log("normalize: dialing within the country"); - if (options.defaultAreaCode) { - if (destinationPlan.getPlanStyle() === "open") { - if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { - // call is not local to this area code, so you have to dial the trunk code and the area code - norm.trunkAccess = destinationPlan.getTrunkCode(); - } - } else { - // In closed plans, you always have to dial the area code, even if the call is local. - if (!norm._hasPrefix()) { - if (destinationLocale.getRegion() === homeLocale.getRegion()) { - norm.areaCode = options.defaultAreaCode; - if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { - norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); - } - } - } else { - if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { - norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); - } - } - } - } - - if (options.sms && - homeLocale.getRegion() === "US" && - currentLocale.getRegion() !== "US") { - norm.iddPrefix = "011"; // make it go through the US first - if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { - delete norm.trunkAccess; - } - } else if (norm.iddPrefix || norm.countryCode) { - // we are in our destination country, so strip the international dialling prefixes - delete norm.iddPrefix; - delete norm.countryCode; - - if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { - norm.trunkAccess = destinationPlan.getTrunkCode(); - } - } - } - } - } else if (!norm.invalid) { - // console.log("normalize: non-assisted normalization"); - if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { - norm.areaCode = options.defaultAreaCode; - } - - if (!norm.countryCode && norm._hasPrefix()) { - norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); - } - - if (norm.countryCode) { - if (options && options.networkType && options.networkType === "cdma") { - norm.iddPrefix = currentPlan.getIDDCode(); - } else { - // all umts carriers support plus dialing - norm.iddPrefix = "+"; - } - - if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { - delete norm.trunkAccess; - } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { - norm.trunkAccess = destinationPlan.getTrunkCode(); - } - } - } - - // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); - formatted = norm._join(); - - return formatted; - }, - - /** - * @private - * @param {{ - * mcc:string, - * defaultAreaCode:string, - * country:string, - * networkType:string, - * assistedDialing:boolean, - * sms:boolean, - * manualDialing:boolean - * }} options an object containing options to help in normalizing. - * @param {PhoneNumber} norm - * @param {PhoneLocale} homeLocale - * @param {PhoneLocale} currentLocale - * @param {NumberingPlan} currentPlan - * @param {PhoneLocale} destinationLocale - * @param {NumberingPlan} destinationPlan - * @param {boolean} sync - * @param {Object|undefined} loadParams - * @param {function(string)} callback - */ - _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { - var formatted, - tempRegion; - - if (options && - options.assistedDialing && - !norm.trunkAccess && - !norm.iddPrefix && - norm.subscriberNumber && - norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { - - // numbers that are too long are sometimes international direct dialed numbers that - // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. - new PhoneNumber("+" + this._join(), { - locale: this.locale, - sync: sync, - loadParms: loadParams, - onLoad: ilib.bind(this, function (data) { - tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); - - if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { - // only use it if it is a recognized country code. Singapore (SG) is a special case. - norm = data; - destinationLocale = data.destinationLocale; - destinationPlan = data.destinationPlan; - } - - formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); - if (typeof(callback) === 'function') { - callback(formatted); - } - }) - }); - } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { - // if this number is not valid for the locale it was parsed with, try again with the current locale - // console.log("norm is invalid. Attempting to reparse with the current locale"); - - new PhoneNumber(this._join(), { - locale: currentLocale, - sync: sync, - loadParms: loadParams, - onLoad: ilib.bind(this, function (data) { - if (data && !data.invalid) { - norm = data; - } - - formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); - if (typeof(callback) === 'function') { - callback(formatted); - } - }) - }); - } else { - formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); - if (typeof(callback) === 'function') { - callback(formatted); - } - } - }, - - /** - * This function normalizes the current phone number to a canonical format and returns a - * string with that phone number. If parts are missing, this function attempts to fill in - * those parts.

    - * - * The options object contains a set of properties that can possibly help normalize - * this number by providing "extra" information to the algorithm. The options - * parameter may be null or an empty object if no hints can be determined before - * this call is made. If any particular hint is not - * available, it does not need to be present in the options object.

    - * - * The following is a list of hints that the algorithm will look for in the options - * object: - * - *

      - *
    • mcc the mobile carrier code of the current network upon which this - * phone is operating. This is translated into an IDD country code. This is - * useful if the number being normalized comes from CNAP (callerid) and the - * MCC is known. - *
    • defaultAreaCode the area code of the phone number of the current - * device, if available. Local numbers in a person's contact list are most - * probably in this same area code. - *
    • country the 2 letter ISO 3166 code of the country if it is - * known from some other means such as parsing the physical address of the - * person associated with the phone number, or the from the domain name - * of the person's email address - *
    • networkType specifies whether the phone is currently connected to a - * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". - * If one of those two strings are not specified, or if this property is left off - * completely, this method will assume UMTS. - *
    - * - * The following are a list of options that control the behaviour of the normalization: - * - *
      - *
    • assistedDialing if this is set to true, the number will be normalized - * so that it can dialled directly on the type of network this phone is - * currently connected to. This allows customers to dial numbers or use numbers - * in their contact list that are specific to their "home" region when they are - * roaming and those numbers would not otherwise work with the current roaming - * carrier as they are. The home region is - * specified as the phoneRegion system preference that is settable in the - * regional settings app. With assisted dialling, this method will add or - * remove international direct dialling prefixes and country codes, as well as - * national trunk access codes, as required by the current roaming carrier and the - * home region in order to dial the number properly. If it is not possible to - * construct a full international dialling sequence from the options and hints given, - * this function will not modify the phone number, and will return "undefined". - * If assisted dialling is false or not specified, then this method will attempt - * to add all the information it can to the number so that it is as fully - * specified as possible. This allows two numbers to be compared more easily when - * those two numbers were otherwise only partially specified. - *
    • sms set this option to true for the following conditions: - *
        - *
      • assisted dialing is turned on - *
      • the phone number represents the destination of an SMS message - *
      • the phone is UMTS - *
      • the phone is SIM-locked to its carrier - *
      - * This enables special international direct dialling codes to route the SMS message to - * the correct carrier. If assisted dialling is not turned on, this option has no - * affect. - *
    • manualDialing set this option to true if the user is entering this number on - * the keypad directly, and false when the number comes from a stored location like a - * contact entry or a call log entry. When true, this option causes the normalizer to - * not perform any normalization on numbers that look like local numbers in the home - * country. If false, all numbers go through normalization. This option only has an effect - * when the assistedDialing option is true as well, otherwise it is ignored. - *
    - * - * If both a set of options and a locale are given, and they offer conflicting - * information, the options will take precedence. The idea is that the locale - * tells you the region setting that the user has chosen (probably in - * firstuse), whereas the the hints are more current information such as - * where the phone is currently operating (the MCC).

    - * - * This function performs the following types of normalizations with assisted - * dialling turned on: - * - *

      - *
    1. If the current location of the phone matches the home country, this is a - * domestic call. - *
        - *
      • Remove any iddPrefix and countryCode fields, as they are not needed - *
      • Add in a trunkAccess field that may be necessary to call a domestic numbers - * in the home country - *
      - *
    2. If the current location of the phone does not match the home country, - * attempt to form a whole international number. - *
        - *
      • Add in the area code if it is missing from the phone number and the area code - * of the current phone is available in the hints - *
      • Add the country dialling code for the home country if it is missing from the - * phone number - *
      • Add or replace the iddPrefix with the correct one for the current country. The - * phone number will have been parsed with the settings for the home country, so - * the iddPrefix may be incorrect for the - * current country. The iddPrefix for the current country can be "+" if the phone - * is connected to a UMTS network, and either a "+" or a country-dependent - * sequences of digits for CDMA networks. - *
      - *
    - * - * This function performs the following types of normalization with assisted - * dialling turned off: - * - *
      - *
    • Normalize the international direct dialing prefix to be a plus or the - * international direct dialling access code for the current country, depending - * on the network type. - *
    • If a number is a local number (ie. it is missing its area code), - * use a default area code from the hints if available. CDMA phones always know their area - * code, and GSM/UMTS phones know their area code in many instances, but not always - * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, - * do not add it. - *
    • In assisted dialling mode, if a number is missing its country code, - * use the current MCC number if - * it is available to figure out the current country code, and prepend that - * to the number. If it is not available, leave it off. Also, use that - * country's settings to parse the number instead of the current format - * locale. - *
    • For North American numbers with an area code but no trunk access - * code, add in the trunk access code. - *
    • For other countries, if the country code is added in step 3, remove the - * trunk access code when required by that country's conventions for - * international calls. If the country requires a trunk access code for - * international calls and it doesn't exist, add one. - *
    - * - * This method modifies the current object, and also returns a string - * containing the normalized phone number that can be compared directly against - * other normalized numbers. The canonical format for phone numbers that is - * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string - * of dialable digits. - * - * @param {{ - * mcc:string, - * defaultAreaCode:string, - * country:string, - * networkType:string, - * assistedDialing:boolean, - * sms:boolean, - * manualDialing:boolean - * }} options an object containing options to help in normalizing. - * @return {string|undefined} the normalized string, or undefined if the number - * could not be normalized - */ - normalize: function(options) { - var norm, - sync = true, - loadParams = {}; - - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (options.loadParams) { - loadParams = options.loadParams; - } - } - - // Clone this number, so we don't mess with the original. - // No need to do this asynchronously because it's a copy constructor which doesn't - // load any extra files. - norm = new PhoneNumber(this); - - var normalized; - - if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { - new PhoneLocale({ - mcc: options.mcc, - countryCode: options.countryCode, - locale: this.locale, - sync: sync, - loadParams: loadParams, - onLoad: ilib.bind(this, function(loc) { - new NumberingPlan({ - locale: loc, - sync: sync, - loadParms: loadParams, - onLoad: ilib.bind(this, function (plan) { - this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { - normalized = fmt; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(fmt); - } - }); - }) - }); - }) - }); - } else { - this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { - normalized = fmt; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(fmt); - } - }); - } - - // return the value for the synchronous case - return normalized; - } + /** + * @protected + * @param {string} number + * @param {Object} regionData + * @param {Object} options + * @param {string} countryCode + */ + _parseOtherCountry: function(number, regionData, options, countryCode) { + new PhoneLocale({ + locale: this.locale, + countryCode: countryCode, + sync: this.sync, + loadParms: this.loadParams, + onLoad: ilib.bind(this, function (loc) { + /* + * this.locale is the locale where this number is being parsed, + * and is used to parse the IDD prefix, if any, and this.destinationLocale is + * the locale of the rest of this number after the IDD prefix. + */ + /** @type {PhoneLocale} */ + this.destinationLocale = loc; + + Utils.loadData({ + name: "states.json", + object: "PhoneNumber", + locale: this.destinationLocale, + sync: this.sync, + loadParams: JSUtils.merge(this.loadParams, { + returnOne: true + }), + callback: ilib.bind(this, function (stateData) { + if (!stateData) { + stateData = PhoneNumber._defaultStates; + } + + new NumberingPlan({ + locale: this.destinationLocale, + sync: this.sync, + loadParms: this.loadParams, + onLoad: ilib.bind(this, function (plan) { + /* + * this.plan is the plan where this number is being parsed, + * and is used to parse the IDD prefix, if any, and this.destinationPlan is + * the plan of the rest of this number after the IDD prefix in the + * destination locale. + */ + /** @type {NumberingPlan} */ + this.destinationPlan = plan; + + var regionSettings = { + stateData: stateData, + plan: plan, + handler: PhoneHandlerFactory(this.destinationLocale, plan) + }; + + // for plans that do not skip the trunk code when dialing from + // abroad, we need to treat the number from here on in as if it + // were parsing a local number from scratch. That way, the parser + // does not get confused between parts of the number at the + // beginning of the number, and parts in the middle. + if (!plan.getSkipTrunk()) { + number = '^' + number; + } + + // recursively call the parser with the new states data + // to finish the parsing + this._parseNumber(number, regionSettings, options); + }) + }); + }) + }); + }) + }); + }, + + /** + * @protected + * @param {string} number + * @param {Object} regionData + * @param {Object} options + */ + _parseNumber: function(number, regionData, options) { + var i, ch, + regionSettings, + newState, + dot, + handlerMethod, + result, + currentState = regionData.stateData, + lastLeaf = undefined, + consumed = 0; + + regionSettings = regionData; + dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java + + i = 0; + while (i < number.length) { + ch = PhoneNumber._getCharacterCode(number.charAt(i)); + if (ch >= 0) { + // newState = stateData.states[state][ch]; + newState = currentState.s && currentState.s[ch]; + + if (!newState && currentState.s && currentState.s[dot]) { + newState = currentState.s[dot]; + } + + if (typeof(newState) === 'object' && i+1 < number.length) { + if (typeof(newState.l) !== 'undefined') { + // this is a leaf node, so save that for later if needed + lastLeaf = newState; + consumed = i; + } + // console.info("recognized digit " + ch + " continuing..."); + // recognized digit, so continue parsing + currentState = newState; + i++; + } else { + if ((typeof(newState) === 'undefined' || newState === 0 || + (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && + lastLeaf) { + // this is possibly a look-ahead and it didn't work... + // so fall back to the last leaf and use that as the + // final state + newState = lastLeaf; + i = consumed; + consumed = 0; + lastLeaf = undefined; + } + + if ((typeof(newState) === 'number' && newState) || + (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { + // final state + var stateNumber = typeof(newState) === 'number' ? newState : newState.l; + handlerMethod = PhoneNumber._states[stateNumber]; + + if (number.charAt(0) === '^') { + result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); + } else { + result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); + } + + // reparse whatever is left + number = result.number; + i = consumed = 0; + lastLeaf = undefined; + + //console.log("reparsing with new number: " + number); + currentState = regionSettings.stateData; + // if the handler requested a special sub-table, use it for this round of parsing, + // otherwise, set it back to the regular table to continue parsing + + if (result.countryCode !== undefined) { + this._parseOtherCountry(number, regionData, options, result.countryCode); + // don't process any further -- let the work be done in the onLoad callbacks + return; + } else if (result.table !== undefined) { + Utils.loadData({ + name: result.table + ".json", + object: "PhoneNumber", + nonlocale: true, + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (data) { + if (!data) { + data = PhoneNumber._defaultStates; + } + + regionSettings = { + stateData: data, + plan: regionSettings.plan, + handler: regionSettings.handler + }; + + // recursively call the parser with the new states data + // to finish the parsing + this._parseNumber(number, regionSettings, options); + }) + }); + // don't process any further -- let the work be done in the onLoad callbacks + return; + } else if (result.skipTrunk !== undefined) { + ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); + currentState = currentState.s && currentState.s[ch]; + } + } else { + handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; + // failed parse. Either no last leaf to fall back to, or there was an explicit + // zero in the table. Put everything else in the subscriberNumber field as the + // default place + if (number.charAt(0) === '^') { + result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); + } else { + result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); + } + break; + } + } + } else if (ch === -1) { + // non-transition character, continue parsing in the same state + i++; + } else { + // should not happen + // console.info("skipping character " + ch); + // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. + i++; + } + } + if (i >= number.length && currentState !== regionData.stateData) { + handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; + // we reached the end of the phone number, but did not finish recognizing anything. + // Default to last resort and put everything that is left into the subscriber number + //console.log("Reached end of number before parsing was complete. Using handler for method none.") + if (number.charAt(0) === '^') { + result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); + } else { + result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); + } + } + + // let the caller know we are done parsing + if (this.onLoad) { + this.onLoad(this); + } + }, + /** + * @protected + */ + _getPrefix: function() { + return this.areaCode || this.serviceCode || this.mobilePrefix || ""; + }, + + /** + * @protected + */ + _hasPrefix: function() { + return (this._getPrefix() !== ""); + }, + + /** + * Exclusive or -- return true, if one is defined and the other isn't + * @protected + */ + _xor : function(left, right) { + if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { + return false; + } else { + return true; + } + }, + + /** + * return a version of the phone number that contains only the dialable digits in the correct order + * @protected + */ + _join: function () { + var fieldName, formatted = ""; + + try { + for (var field in PhoneNumber._fieldOrder) { + if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { + fieldName = PhoneNumber._fieldOrder[field]; + // console.info("normalize: formatting field " + fieldName); + if (this[fieldName] !== undefined) { + formatted += this[fieldName]; + } + } + } + } catch ( e ) { + //console.warn("caught exception in _join: " + e); + throw e; + } + return formatted; + }, + + /** + * This routine will compare the two phone numbers in an locale-sensitive + * manner to see if they possibly reference the same phone number.

    + * + * In many places, + * there are multiple ways to reach the same phone number. In North America for + * example, you might have a number with the trunk access code of "1" and another + * without, and they reference the exact same phone number. This is considered a + * strong match. For a different pair of numbers, one may be a local number and + * the other a full phone number with area code, which may reference the same + * phone number if the local number happens to be located in that area code. + * However, you cannot say for sure if it is in that area code, so it will + * be considered a somewhat weaker match.

    + * + * Similarly, in other countries, there are sometimes different ways of + * reaching the same destination, and the way that numbers + * match depends on the locale.

    + * + * The various phone number fields are handled differently for matches. There + * are various fields that do not need to match at all. For example, you may + * type equally enter "00" or "+" into your phone to start international direct + * dialling, so the iddPrefix field does not need to match at all.

    + * + * Typically, fields that require matches need to match exactly if both sides have a value + * for that field. If both sides specify a value and those values differ, that is + * a strong non-match. If one side does not have a value and the other does, that + * causes a partial match, because the number with the missing field may possibly + * have an implied value that matches the other number. For example, the numbers + * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" + * might possibly have the same 650 area code as the first number, and might possibly + * not. If both side do not specify a value for a particular field, that field is + * considered matching.

    + * + * The values of following fields are ignored when performing matches: + * + *

      + *
    • vsc + *
    • iddPrefix + *
    • cic + *
    • trunkAccess + *
    + * + * The values of the following fields matter if they do not match: + * + *
      + *
    • countryCode - A difference causes a moderately strong problem except for + * certain countries where there is a way to access the same subscriber via IDD + * and via intranetwork dialling + *
    • mobilePrefix - A difference causes a possible non-match + *
    • serviceCode - A difference causes a possible non-match + *
    • areaCode - A difference causes a possible non-match + *
    • subscriberNumber - A difference causes a very strong non-match + *
    • extension - A difference causes a minor non-match + *
    + * + * @param {string|PhoneNumber} other other phone number to compare this one to + * @return {number} non-negative integer describing the percentage quality of the + * match. 100 means a very strong match (100%), and lower numbers are less and + * less strong, down to 0 meaning not at all a match. + */ + compare: function (other) { + var match = 100, + FRdepartments = {"590":1, "594":1, "596":1, "262":1}, + ITcountries = {"378":1, "379":1}, + thisPrefix, + otherPrefix, + currentCountryCode = 0; + + if (typeof this.locale.region === "string") { + currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); + } + + // subscriber number must be present and must match + if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { + return 0; + } + + // extension must match if it is present + if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { + return 0; + } + + if (this._xor(this.countryCode, other.countryCode)) { + // if one doesn't have a country code, give it some demerit points, but if the + // one that has the country code has something other than the current country + // add even more. Ignore the special cases where you can dial the same number internationally or via + // the local numbering system + switch (this.locale.getRegion()) { + case 'FR': + if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { + if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { + match -= 100; + } + } else { + match -= 16; + } + break; + case 'IT': + if (this.countryCode in ITcountries || other.countryCode in ITcountries) { + if (this.areaCode !== other.areaCode) { + match -= 100; + } + } else { + match -= 16; + } + break; + default: + match -= 16; + if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || + (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { + match -= 16; + } + } + } else if (this.countryCode !== other.countryCode) { + // ignore the special cases where you can dial the same number internationally or via + // the local numbering system + if (other.countryCode === '33' || this.countryCode === '33') { + // france + if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { + if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { + match -= 100; + } + } else { + match -= 100; + } + } else if (this.countryCode === '39' || other.countryCode === '39') { + // italy + if (this.countryCode in ITcountries || other.countryCode in ITcountries) { + if (this.areaCode !== other.areaCode) { + match -= 100; + } + } else { + match -= 100; + } + } else { + match -= 100; + } + } + + if (this._xor(this.serviceCode, other.serviceCode)) { + match -= 20; + } else if (this.serviceCode !== other.serviceCode) { + match -= 100; + } + + if (this._xor(this.mobilePrefix, other.mobilePrefix)) { + match -= 20; + } else if (this.mobilePrefix !== other.mobilePrefix) { + match -= 100; + } + + if (this._xor(this.areaCode, other.areaCode)) { + // one has an area code, the other doesn't, so dock some points. It could be a match if the local + // number in the one number has the same implied area code as the explicit area code in the other number. + match -= 12; + } else if (this.areaCode !== other.areaCode) { + match -= 100; + } + + thisPrefix = this._getPrefix(); + otherPrefix = other._getPrefix(); + + if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { + match -= 100; + } + + // make sure we are between 0 and 100 + if (match < 0) { + match = 0; + } else if (match > 100) { + match = 100; + } + + return match; + }, + + /** + * Determine whether or not the other phone number is exactly equal to the current one.

    + * + * The difference between the compare method and the equals method is that the compare + * method compares normalized numbers with each other and returns the degree of match, + * whereas the equals operator returns true iff the two numbers contain the same fields + * and the fields are exactly the same. Functions and other non-phone number properties + * are not compared. + * @param {string|PhoneNumber} other another phone number to compare to this one + * @return {boolean} true if the numbers are the same, false otherwise + */ + equals: function equals(other) { + if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { + return false; + } + + var _this = this; + return PhoneNumber._fieldOrder.every(function(field) { + return _this[field] === other[field]; + }); + }, + + /** + * @private + * @param {{ + * mcc:string, + * defaultAreaCode:string, + * country:string, + * networkType:string, + * assistedDialing:boolean, + * sms:boolean, + * manualDialing:boolean + * }} options an object containing options to help in normalizing. + * @param {PhoneNumber} norm + * @param {PhoneLocale} homeLocale + * @param {PhoneLocale} currentLocale + * @param {NumberingPlan} currentPlan + * @param {PhoneLocale} destinationLocale + * @param {NumberingPlan} destinationPlan + * @param {boolean} sync + * @param {Object|undefined} loadParams + */ + _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { + var formatted = ""; + + if (!norm.invalid && options && options.assistedDialing) { + // don't normalize things that don't have subscriber numbers. Also, don't normalize + // manually dialed local numbers. Do normalize local numbers in contact entries. + if (norm.subscriberNumber && + (!options.manualDialing || + norm.iddPrefix || + norm.countryCode || + norm.trunkAccess)) { + // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); + if (currentLocale.getRegion() !== destinationLocale.getRegion()) { + // we are currently calling internationally + if (!norm._hasPrefix() && + options.defaultAreaCode && + destinationLocale.getRegion() === homeLocale.getRegion() && + (!destinationPlan.getFieldLength("minLocalLength") || + norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { + // area code is required when dialling from international, but only add it if we are dialing + // to our home area. Otherwise, the default area code is not valid! + norm.areaCode = options.defaultAreaCode; + if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { + // some phone systems require the trunk access code, even when dialling from international + norm.trunkAccess = destinationPlan.getTrunkCode(); + } + } + + if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { + // on some phone systems, the trunk access code is dropped when dialling from international + delete norm.trunkAccess; + } + + // make sure to get the country code for the destination region, not the current region! + if (options.sms) { + if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { + if (destinationLocale.getRegion() !== "US") { + norm.iddPrefix = "011"; // non-standard code to make it go through the US first + norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); + } else if (options.networkType === "cdma") { + delete norm.iddPrefix; + delete norm.countryCode; + if (norm.areaCode) { + norm.trunkAccess = "1"; + } + } else if (norm.areaCode) { + norm.iddPrefix = "+"; + norm.countryCode = "1"; + delete norm.trunkAccess; + } + } else { + norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; + norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); + } + } else if (norm._hasPrefix() && !norm.countryCode) { + norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); + } + + if (norm.countryCode && !options.sms) { + // for CDMA, make sure to get the international dialling access code for the current region, not the destination region + // all umts carriers support plus dialing + norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; + } + } else { + // console.log("normalize: dialing within the country"); + if (options.defaultAreaCode) { + if (destinationPlan.getPlanStyle() === "open") { + if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { + // call is not local to this area code, so you have to dial the trunk code and the area code + norm.trunkAccess = destinationPlan.getTrunkCode(); + } + } else { + // In closed plans, you always have to dial the area code, even if the call is local. + if (!norm._hasPrefix()) { + if (destinationLocale.getRegion() === homeLocale.getRegion()) { + norm.areaCode = options.defaultAreaCode; + if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { + norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); + } + } + } else { + if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { + norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); + } + } + } + } + + if (options.sms && + homeLocale.getRegion() === "US" && + currentLocale.getRegion() !== "US") { + norm.iddPrefix = "011"; // make it go through the US first + if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { + delete norm.trunkAccess; + } + } else if (norm.iddPrefix || norm.countryCode) { + // we are in our destination country, so strip the international dialling prefixes + delete norm.iddPrefix; + delete norm.countryCode; + + if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { + norm.trunkAccess = destinationPlan.getTrunkCode(); + } + } + } + } + } else if (!norm.invalid) { + // console.log("normalize: non-assisted normalization"); + if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { + norm.areaCode = options.defaultAreaCode; + } + + if (!norm.countryCode && norm._hasPrefix()) { + norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); + } + + if (norm.countryCode) { + if (options && options.networkType && options.networkType === "cdma") { + norm.iddPrefix = currentPlan.getIDDCode(); + } else { + // all umts carriers support plus dialing + norm.iddPrefix = "+"; + } + + if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { + delete norm.trunkAccess; + } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { + norm.trunkAccess = destinationPlan.getTrunkCode(); + } + } + } + + // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); + formatted = norm._join(); + + return formatted; + }, + + /** + * @private + * @param {{ + * mcc:string, + * defaultAreaCode:string, + * country:string, + * networkType:string, + * assistedDialing:boolean, + * sms:boolean, + * manualDialing:boolean + * }} options an object containing options to help in normalizing. + * @param {PhoneNumber} norm + * @param {PhoneLocale} homeLocale + * @param {PhoneLocale} currentLocale + * @param {NumberingPlan} currentPlan + * @param {PhoneLocale} destinationLocale + * @param {NumberingPlan} destinationPlan + * @param {boolean} sync + * @param {Object|undefined} loadParams + * @param {function(string)} callback + */ + _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { + var formatted, + tempRegion; + + if (options && + options.assistedDialing && + !norm.trunkAccess && + !norm.iddPrefix && + norm.subscriberNumber && + norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { + + // numbers that are too long are sometimes international direct dialed numbers that + // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. + new PhoneNumber("+" + this._join(), { + locale: this.locale, + sync: sync, + loadParms: loadParams, + onLoad: ilib.bind(this, function (data) { + tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); + + if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { + // only use it if it is a recognized country code. Singapore (SG) is a special case. + norm = data; + destinationLocale = data.destinationLocale; + destinationPlan = data.destinationPlan; + } + + formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); + if (typeof(callback) === 'function') { + callback(formatted); + } + }) + }); + } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { + // if this number is not valid for the locale it was parsed with, try again with the current locale + // console.log("norm is invalid. Attempting to reparse with the current locale"); + + new PhoneNumber(this._join(), { + locale: currentLocale, + sync: sync, + loadParms: loadParams, + onLoad: ilib.bind(this, function (data) { + if (data && !data.invalid) { + norm = data; + } + + formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); + if (typeof(callback) === 'function') { + callback(formatted); + } + }) + }); + } else { + formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); + if (typeof(callback) === 'function') { + callback(formatted); + } + } + }, + + /** + * This function normalizes the current phone number to a canonical format and returns a + * string with that phone number. If parts are missing, this function attempts to fill in + * those parts.

    + * + * The options object contains a set of properties that can possibly help normalize + * this number by providing "extra" information to the algorithm. The options + * parameter may be null or an empty object if no hints can be determined before + * this call is made. If any particular hint is not + * available, it does not need to be present in the options object.

    + * + * The following is a list of hints that the algorithm will look for in the options + * object: + * + *

      + *
    • mcc the mobile carrier code of the current network upon which this + * phone is operating. This is translated into an IDD country code. This is + * useful if the number being normalized comes from CNAP (callerid) and the + * MCC is known. + *
    • defaultAreaCode the area code of the phone number of the current + * device, if available. Local numbers in a person's contact list are most + * probably in this same area code. + *
    • country the 2 letter ISO 3166 code of the country if it is + * known from some other means such as parsing the physical address of the + * person associated with the phone number, or the from the domain name + * of the person's email address + *
    • networkType specifies whether the phone is currently connected to a + * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". + * If one of those two strings are not specified, or if this property is left off + * completely, this method will assume UMTS. + *
    + * + * The following are a list of options that control the behaviour of the normalization: + * + *
      + *
    • assistedDialing if this is set to true, the number will be normalized + * so that it can dialled directly on the type of network this phone is + * currently connected to. This allows customers to dial numbers or use numbers + * in their contact list that are specific to their "home" region when they are + * roaming and those numbers would not otherwise work with the current roaming + * carrier as they are. The home region is + * specified as the phoneRegion system preference that is settable in the + * regional settings app. With assisted dialling, this method will add or + * remove international direct dialling prefixes and country codes, as well as + * national trunk access codes, as required by the current roaming carrier and the + * home region in order to dial the number properly. If it is not possible to + * construct a full international dialling sequence from the options and hints given, + * this function will not modify the phone number, and will return "undefined". + * If assisted dialling is false or not specified, then this method will attempt + * to add all the information it can to the number so that it is as fully + * specified as possible. This allows two numbers to be compared more easily when + * those two numbers were otherwise only partially specified. + *
    • sms set this option to true for the following conditions: + *
        + *
      • assisted dialing is turned on + *
      • the phone number represents the destination of an SMS message + *
      • the phone is UMTS + *
      • the phone is SIM-locked to its carrier + *
      + * This enables special international direct dialling codes to route the SMS message to + * the correct carrier. If assisted dialling is not turned on, this option has no + * affect. + *
    • manualDialing set this option to true if the user is entering this number on + * the keypad directly, and false when the number comes from a stored location like a + * contact entry or a call log entry. When true, this option causes the normalizer to + * not perform any normalization on numbers that look like local numbers in the home + * country. If false, all numbers go through normalization. This option only has an effect + * when the assistedDialing option is true as well, otherwise it is ignored. + *
    + * + * If both a set of options and a locale are given, and they offer conflicting + * information, the options will take precedence. The idea is that the locale + * tells you the region setting that the user has chosen (probably in + * firstuse), whereas the the hints are more current information such as + * where the phone is currently operating (the MCC).

    + * + * This function performs the following types of normalizations with assisted + * dialling turned on: + * + *

      + *
    1. If the current location of the phone matches the home country, this is a + * domestic call. + *
        + *
      • Remove any iddPrefix and countryCode fields, as they are not needed + *
      • Add in a trunkAccess field that may be necessary to call a domestic numbers + * in the home country + *
      + *
    2. If the current location of the phone does not match the home country, + * attempt to form a whole international number. + *
        + *
      • Add in the area code if it is missing from the phone number and the area code + * of the current phone is available in the hints + *
      • Add the country dialling code for the home country if it is missing from the + * phone number + *
      • Add or replace the iddPrefix with the correct one for the current country. The + * phone number will have been parsed with the settings for the home country, so + * the iddPrefix may be incorrect for the + * current country. The iddPrefix for the current country can be "+" if the phone + * is connected to a UMTS network, and either a "+" or a country-dependent + * sequences of digits for CDMA networks. + *
      + *
    + * + * This function performs the following types of normalization with assisted + * dialling turned off: + * + *
      + *
    • Normalize the international direct dialing prefix to be a plus or the + * international direct dialling access code for the current country, depending + * on the network type. + *
    • If a number is a local number (ie. it is missing its area code), + * use a default area code from the hints if available. CDMA phones always know their area + * code, and GSM/UMTS phones know their area code in many instances, but not always + * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, + * do not add it. + *
    • In assisted dialling mode, if a number is missing its country code, + * use the current MCC number if + * it is available to figure out the current country code, and prepend that + * to the number. If it is not available, leave it off. Also, use that + * country's settings to parse the number instead of the current format + * locale. + *
    • For North American numbers with an area code but no trunk access + * code, add in the trunk access code. + *
    • For other countries, if the country code is added in step 3, remove the + * trunk access code when required by that country's conventions for + * international calls. If the country requires a trunk access code for + * international calls and it doesn't exist, add one. + *
    + * + * This method modifies the current object, and also returns a string + * containing the normalized phone number that can be compared directly against + * other normalized numbers. The canonical format for phone numbers that is + * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string + * of dialable digits. + * + * @param {{ + * mcc:string, + * defaultAreaCode:string, + * country:string, + * networkType:string, + * assistedDialing:boolean, + * sms:boolean, + * manualDialing:boolean + * }} options an object containing options to help in normalizing. + * @return {string|undefined} the normalized string, or undefined if the number + * could not be normalized + */ + normalize: function(options) { + var norm, + sync = true, + loadParams = {}; + + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (options.loadParams) { + loadParams = options.loadParams; + } + } + + // Clone this number, so we don't mess with the original. + // No need to do this asynchronously because it's a copy constructor which doesn't + // load any extra files. + norm = new PhoneNumber(this); + + var normalized; + + if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { + new PhoneLocale({ + mcc: options.mcc, + countryCode: options.countryCode, + locale: this.locale, + sync: sync, + loadParams: loadParams, + onLoad: ilib.bind(this, function(loc) { + new NumberingPlan({ + locale: loc, + sync: sync, + loadParms: loadParams, + onLoad: ilib.bind(this, function (plan) { + this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { + normalized = fmt; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(fmt); + } + }); + }) + }); + }) + }); + } else { + this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { + normalized = fmt; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(fmt); + } + }); + } + + // return the value for the synchronous case + return normalized; + } }; module.exports = PhoneNumber; \ No newline at end of file diff --git a/js/lib/QMLLoader.js b/js/lib/QMLLoader.js index 50778eb4b0..62bbc2b9d7 100644 --- a/js/lib/QMLLoader.js +++ b/js/lib/QMLLoader.js @@ -1,6 +1,6 @@ /* - * QMLLoader.js - loader implementation for Qt/QML apps. - * + * QMLLoader.js - loader implementation for Qt/QML apps. + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,38 +24,38 @@ var Loader = require("./Loader.js"); /** * @class * QML implemenation of a Loader class. - * + * * @private * @extends Loader * @param {Object} fr the Qt FileReader instance */ var QMLLoader = function(fr) { - //console.log("new QMLLoader instance called with " + fr); - this.fr = fr; - - this.parent.call(this, ilib); - - this.root = module.filename ? path.dirname(path.join(module.filename, "..")) : Qt.resolvedUrl("..").toString(); - this.root = this.root.replace("file://", ""); - //console.log("QMLLoader using root: " + root); - - if (this.root[this.root.length-1] === '/') { - this.root = this.root.substring(0, this.root.length-1); - } - + //console.log("new QMLLoader instance called with " + fr); + this.fr = fr; + + this.parent.call(this, ilib); + + this.root = module.filename ? path.dirname(path.join(module.filename, "..")) : Qt.resolvedUrl("..").toString(); + this.root = this.root.replace("file://", ""); + //console.log("QMLLoader using root: " + root); + + if (this.root[this.root.length-1] === '/') { + this.root = this.root.substring(0, this.root.length-1); + } + + + this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first + + // then a standard locale dir of a built version of ilib from npm + this._exists(path.join(this.root, "locale"), "localeinfo.json"); + + // try the standard install directories + this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); + + // ... else fall back to see if we're in a check-out dir of ilib + // this._exists(path.join(this.root, "data", "locale"), "localeinfo.json"); - this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first - - // then a standard locale dir of a built version of ilib from npm - this._exists(path.join(this.root, "locale"), "localeinfo.json"); - - // try the standard install directories - this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); - - // ... else fall back to see if we're in a check-out dir of ilib - // this._exists(path.join(this.root, "data", "locale"), "localeinfo.json"); - - //console.log("QMLLoader: include path is now " + JSON.stringify(this.includePath)); + //console.log("QMLLoader: include path is now " + JSON.stringify(this.includePath)); }; QMLLoader.prototype = new Loader(); @@ -63,25 +63,25 @@ QMLLoader.prototype.parent = Loader; QMLLoader.prototype.constructor = QMLLoader; QMLLoader.prototype._loadFile = function (pathname, sync, cb) { - //console.log("_loadFile: attempting to load " + pathname); - // use the FileReader plugin to access the local disk synchronously - if (this.fr.exists(pathname)) { - var text = this.fr.read(pathname); - cb && typeof(cb) === 'function' && cb(text); - return text; - } else { - cb && typeof(cb) === 'function' && cb(); - return undefined; - } + //console.log("_loadFile: attempting to load " + pathname); + // use the FileReader plugin to access the local disk synchronously + if (this.fr.exists(pathname)) { + var text = this.fr.read(pathname); + cb && typeof(cb) === 'function' && cb(text); + return text; + } else { + cb && typeof(cb) === 'function' && cb(); + return undefined; + } }; QMLLoader.prototype._exists = function(dir, file) { - var fullpath = path.normalize(path.join(dir, file)); - //console.log("QMLLoader._exists: checking for the existence of " + dir); - if (this.fr.exists(fullpath)) { - //console.log("QMLLoader._exists: found"); - this.includePath.push(dir); - } + var fullpath = path.normalize(path.join(dir, file)); + //console.log("QMLLoader._exists: checking for the existence of " + dir); + if (this.fr.exists(fullpath)) { + //console.log("QMLLoader._exists: found"); + this.includePath.push(dir); + } }; module.exports = QMLLoader; \ No newline at end of file diff --git a/js/lib/RataDie.js b/js/lib/RataDie.js index e03b236a1e..c5c4cd7a48 100644 --- a/js/lib/RataDie.js +++ b/js/lib/RataDie.js @@ -1,6 +1,6 @@ /* * RataDie.js - Represent the RD date number in the calendar - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,92 +23,92 @@ var JulianDay = require("./JulianDay.js"); /** * @class - * Construct a new RD date number object. The constructor parameters can + * Construct a new RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
      - *
    • unixtime - sets the time of this instance according to the given + *
    • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. - * + * *
    • julianday - sets the time of this instance according to the given * Julian Day instance or the Julian Day given as a float - * + * *
    • cycle - any integer giving the number of 60-year cycle in which the date is located. - * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious + * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious * linear count of years since the beginning of the epoch, much like other calendars. This linear - * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 + * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 * to 60 and treated as if it were a year in the regular 60-year cycle. - * + * *
    • year - any integer, including 0 - * + * *
    • month - 1 to 12, where 1 means January, 2 means February, etc. - * + * *
    • day - 1 to 31 - * - *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + * + *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation - * + * *
    • minute - 0 to 59 - * + * *
    • second - 0 to 59 - * + * *
    • millisecond - 0 to 999 - * - *
    • parts - 0 to 1079. Specify the halaqim parts of an hour. Either specify + * + *
    • parts - 0 to 1079. Specify the halaqim parts of an hour. Either specify * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used - * in the Hebrew calendar. - * + * in the Hebrew calendar. + * *
    • minute - 0 to 59 - * + * *
    • date - use the given intrinsic Javascript date to initialize this one. *
    * * If the constructor is called with another date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.

    - * - * If the constructor is called with no arguments at all or if none of the - * properties listed above are present, then the RD is calculate based on + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above are present, then the RD is calculate based on * the current date at the time of instantiation.

    - * + * * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).

    - * - * + * + * * @private * @constructor * @param {Object=} params parameters that govern the settings and behaviour of this RD date */ var RataDie = function(params) { - if (params) { - if (typeof(params.date) !== 'undefined') { - // accept JS Date classes or strings - var date = params.date; - if (!(JSUtils.isDate(date))) { - date = new Date(date); // maybe a string initializer? - } - this._setTime(date.getTime()); - } else if (typeof(params.unixtime) !== 'undefined') { - this._setTime(parseInt(params.unixtime, 10)); - } else if (typeof(params.julianday) !== 'undefined') { - // JD time is defined to be UTC - this._setJulianDay(parseFloat(params.julianday)); - } else if (params.year || params.month || params.day || params.hour || - params.minute || params.second || params.millisecond || params.parts || params.cycle) { - this._setDateComponents(params); - } else if (typeof(params.rd) !== 'undefined') { - /** - * @type {number} the Rata Die number of this date for this calendar type - */ - this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; - } - } - - if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { - var now = new Date(); - this._setTime(now.getTime()); - } + if (params) { + if (typeof(params.date) !== 'undefined') { + // accept JS Date classes or strings + var date = params.date; + if (!(JSUtils.isDate(date))) { + date = new Date(date); // maybe a string initializer? + } + this._setTime(date.getTime()); + } else if (typeof(params.unixtime) !== 'undefined') { + this._setTime(parseInt(params.unixtime, 10)); + } else if (typeof(params.julianday) !== 'undefined') { + // JD time is defined to be UTC + this._setJulianDay(parseFloat(params.julianday)); + } else if (params.year || params.month || params.day || params.hour || + params.minute || params.second || params.millisecond || params.parts || params.cycle) { + this._setDateComponents(params); + } else if (typeof(params.rd) !== 'undefined') { + /** + * @type {number} the Rata Die number of this date for this calendar type + */ + this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; + } + } + + if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { + var now = new Date(); + this._setTime(now.getTime()); + } }; /** @@ -119,190 +119,190 @@ var RataDie = function(params) { RataDie.gregorianEpoch = 1721424.5; RataDie.prototype = { - /** - * @protected - * @type {number} - * the difference between a zero Julian day and the zero Gregorian date. - */ - epoch: RataDie.gregorianEpoch, - - /** - * Set the RD of this instance according to the given unix time. Unix time is - * the number of milliseconds since midnight on Jan 1, 1970. - * - * @protected - * @param {number} millis the unix time to set this date to in milliseconds - */ - _setTime: function(millis) { - // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) - this._setJulianDay(2440587.5 + millis / 86400000); - }, + /** + * @protected + * @type {number} + * the difference between a zero Julian day and the zero Gregorian date. + */ + epoch: RataDie.gregorianEpoch, + + /** + * Set the RD of this instance according to the given unix time. Unix time is + * the number of milliseconds since midnight on Jan 1, 1970. + * + * @protected + * @param {number} millis the unix time to set this date to in milliseconds + */ + _setTime: function(millis) { + // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) + this._setJulianDay(2440587.5 + millis / 86400000); + }, + + /** + * Set the date of this instance using a Julian Day. + * @protected + * @param {number} date the Julian Day to use to set this date + */ + _setJulianDay: function (date) { + var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; + // round to the nearest millisecond + this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; + }, + + /** + * Return the rd number of the particular day of the week on or before the + * given rd. eg. The Sunday on or before the given rd. + * @protected + * @param {number} rd the rata die date of the reference date + * @param {number} dayOfWeek the day of the week that is being sought relative + * to the current date + * @return {number} the rd of the day of the week + */ + _onOrBefore: function(rd, dayOfWeek) { + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); + }, + + /** + * Return the rd number of the particular day of the week on or before the current rd. + * eg. The Sunday on or before the current rd. If the offset is given, the calculation + * happens in wall time instead of UTC. UTC time may be a day before or day behind + * wall time, so it it would give the wrong day of the week if this calculation was + * done in UTC time when the caller really wanted wall time. Even though the calculation + * may be done in wall time, the return value is nonetheless always given in UTC. + * @param {number} dayOfWeek the day of the week that is being sought relative + * to the current date + * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is + * not given + * @return {number} the rd of the day of the week + */ + onOrBefore: function(dayOfWeek, offset) { + offset = offset || 0; + return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; + }, + + /** + * Return the rd number of the particular day of the week on or before the current rd. + * eg. The Sunday on or before the current rd. If the offset is given, the calculation + * happens in wall time instead of UTC. UTC time may be a day before or day behind + * wall time, so it it would give the wrong day of the week if this calculation was + * done in UTC time when the caller really wanted wall time. Even though the calculation + * may be done in wall time, the return value is nonetheless always given in UTC. + * @param {number} dayOfWeek the day of the week that is being sought relative + * to the reference date + * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is + * not given + * @return {number} the day of the week + */ + onOrAfter: function(dayOfWeek, offset) { + offset = offset || 0; + return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; + }, + + /** + * Return the rd number of the particular day of the week before the current rd. + * eg. The Sunday before the current rd. If the offset is given, the calculation + * happens in wall time instead of UTC. UTC time may be a day before or day behind + * wall time, so it it would give the wrong day of the week if this calculation was + * done in UTC time when the caller really wanted wall time. Even though the calculation + * may be done in wall time, the return value is nonetheless always given in UTC. + * @param {number} dayOfWeek the day of the week that is being sought relative + * to the reference date + * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is + * not given + * @return {number} the day of the week + */ + before: function(dayOfWeek, offset) { + offset = offset || 0; + return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; + }, + + /** + * Return the rd number of the particular day of the week after the current rd. + * eg. The Sunday after the current rd. If the offset is given, the calculation + * happens in wall time instead of UTC. UTC time may be a day before or day behind + * wall time, so it it would give the wrong day of the week if this calculation was + * done in UTC time when the caller really wanted wall time. Even though the calculation + * may be done in wall time, the return value is nonetheless always given in UTC. + * @param {number} dayOfWeek the day of the week that is being sought relative + * to the reference date + * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is + * not given + * @return {number} the day of the week + */ + after: function(dayOfWeek, offset) { + offset = offset || 0; + return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; + }, + + /** + * Return the unix time equivalent to this Gregorian date instance. Unix time is + * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only + * returns a valid number for dates between midnight, Jan 1, 1970 and + * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance + * encodes a date outside of that range, this method will return -1. + * + * @return {number} a number giving the unix time, or -1 if the date is outside the + * valid unix time range + */ + getTime: function() { + // earlier than Jan 1, 1970 + // or later than Jan 19, 2038 at 3:14:07am + var jd = this.getJulianDay(); + if (jd < 2440587.5 || jd > 2465442.634803241) { + return -1; + } - /** - * Set the date of this instance using a Julian Day. - * @protected - * @param {number} date the Julian Day to use to set this date - */ - _setJulianDay: function (date) { - var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; - // round to the nearest millisecond - this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; - }, + // avoid the rounding errors in the floating point math by only using + // the whole days from the rd, and then calculating the milliseconds directly + return Math.round((jd - 2440587.5) * 86400000); + }, - /** - * Return the rd number of the particular day of the week on or before the - * given rd. eg. The Sunday on or before the given rd. - * @protected - * @param {number} rd the rata die date of the reference date - * @param {number} dayOfWeek the day of the week that is being sought relative - * to the current date - * @return {number} the rd of the day of the week - */ - _onOrBefore: function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); - }, - - /** - * Return the rd number of the particular day of the week on or before the current rd. - * eg. The Sunday on or before the current rd. If the offset is given, the calculation - * happens in wall time instead of UTC. UTC time may be a day before or day behind - * wall time, so it it would give the wrong day of the week if this calculation was - * done in UTC time when the caller really wanted wall time. Even though the calculation - * may be done in wall time, the return value is nonetheless always given in UTC. - * @param {number} dayOfWeek the day of the week that is being sought relative - * to the current date - * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is - * not given - * @return {number} the rd of the day of the week - */ - onOrBefore: function(dayOfWeek, offset) { - offset = offset || 0; - return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; - }, - - /** - * Return the rd number of the particular day of the week on or before the current rd. - * eg. The Sunday on or before the current rd. If the offset is given, the calculation - * happens in wall time instead of UTC. UTC time may be a day before or day behind - * wall time, so it it would give the wrong day of the week if this calculation was - * done in UTC time when the caller really wanted wall time. Even though the calculation - * may be done in wall time, the return value is nonetheless always given in UTC. - * @param {number} dayOfWeek the day of the week that is being sought relative - * to the reference date - * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is - * not given - * @return {number} the day of the week - */ - onOrAfter: function(dayOfWeek, offset) { - offset = offset || 0; - return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; - }, - - /** - * Return the rd number of the particular day of the week before the current rd. - * eg. The Sunday before the current rd. If the offset is given, the calculation - * happens in wall time instead of UTC. UTC time may be a day before or day behind - * wall time, so it it would give the wrong day of the week if this calculation was - * done in UTC time when the caller really wanted wall time. Even though the calculation - * may be done in wall time, the return value is nonetheless always given in UTC. - * @param {number} dayOfWeek the day of the week that is being sought relative - * to the reference date - * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is - * not given - * @return {number} the day of the week - */ - before: function(dayOfWeek, offset) { - offset = offset || 0; - return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; - }, - - /** - * Return the rd number of the particular day of the week after the current rd. - * eg. The Sunday after the current rd. If the offset is given, the calculation - * happens in wall time instead of UTC. UTC time may be a day before or day behind - * wall time, so it it would give the wrong day of the week if this calculation was - * done in UTC time when the caller really wanted wall time. Even though the calculation - * may be done in wall time, the return value is nonetheless always given in UTC. - * @param {number} dayOfWeek the day of the week that is being sought relative - * to the reference date - * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is - * not given - * @return {number} the day of the week - */ - after: function(dayOfWeek, offset) { - offset = offset || 0; - return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; - }, + /** + * Return the extended unix time equivalent to this Gregorian date instance. Unix time is + * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time + * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus + * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above + * 32 bits and the Date object allows you to encode up to 100 million days worth of time + * after Jan 1, 1970, and even more interestingly 100 million days worth of time before + * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended + * range. If this instance encodes a date outside of that range, this method will return + * NaN. + * + * @return {number} a number giving the extended unix time, or NaN if the date is outside + * the valid extended unix time range + */ + getTimeExtended: function() { + var jd = this.getJulianDay(); - /** - * Return the unix time equivalent to this Gregorian date instance. Unix time is - * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only - * returns a valid number for dates between midnight, Jan 1, 1970 and - * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance - * encodes a date outside of that range, this method will return -1. - * - * @return {number} a number giving the unix time, or -1 if the date is outside the - * valid unix time range - */ - getTime: function() { - // earlier than Jan 1, 1970 - // or later than Jan 19, 2038 at 3:14:07am - var jd = this.getJulianDay(); - if (jd < 2440587.5 || jd > 2465442.634803241) { - return -1; - } - - // avoid the rounding errors in the floating point math by only using - // the whole days from the rd, and then calculating the milliseconds directly - return Math.round((jd - 2440587.5) * 86400000); - }, + // test if earlier than Jan 1, 1970 - 100 million days + // or later than Jan 1, 1970 + 100 million days + if (jd < -97559412.5 || jd > 102440587.5) { + return NaN; + } - /** - * Return the extended unix time equivalent to this Gregorian date instance. Unix time is - * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time - * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus - * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above - * 32 bits and the Date object allows you to encode up to 100 million days worth of time - * after Jan 1, 1970, and even more interestingly 100 million days worth of time before - * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended - * range. If this instance encodes a date outside of that range, this method will return - * NaN. - * - * @return {number} a number giving the extended unix time, or NaN if the date is outside - * the valid extended unix time range - */ - getTimeExtended: function() { - var jd = this.getJulianDay(); - - // test if earlier than Jan 1, 1970 - 100 million days - // or later than Jan 1, 1970 + 100 million days - if (jd < -97559412.5 || jd > 102440587.5) { - return NaN; - } - - // avoid the rounding errors in the floating point math by only using - // the whole days from the rd, and then calculating the milliseconds directly - return Math.round((jd - 2440587.5) * 86400000); - }, + // avoid the rounding errors in the floating point math by only using + // the whole days from the rd, and then calculating the milliseconds directly + return Math.round((jd - 2440587.5) * 86400000); + }, - /** - * Return the Julian Day equivalent to this calendar date as a number. - * This returns the julian day in UTC. - * - * @return {number} the julian date equivalent of this date - */ - getJulianDay: function() { - return this.rd + this.epoch; - }, + /** + * Return the Julian Day equivalent to this calendar date as a number. + * This returns the julian day in UTC. + * + * @return {number} the julian date equivalent of this date + */ + getJulianDay: function() { + return this.rd + this.epoch; + }, - /** - * Return the Rata Die (fixed day) number of this RD date. - * - * @return {number} the rd date as a number - */ - getRataDie: function() { - return this.rd; - } + /** + * Return the Rata Die (fixed day) number of this RD date. + * + * @return {number} the rd date as a number + */ + getRataDie: function() { + return this.rd; + } }; module.exports = RataDie; diff --git a/js/lib/ResBundle.js b/js/lib/ResBundle.js index 0e6e20c453..607ba14cb5 100644 --- a/js/lib/ResBundle.js +++ b/js/lib/ResBundle.js @@ -1,6 +1,6 @@ /* * ResBundle.js - Resource bundle definition - * + * * Copyright © 2012-2016, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data pseudomap -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -31,85 +31,85 @@ var IString = require("./IString.js"); /** * @class * Create a new resource bundle instance. The resource bundle loads strings - * appropriate for a particular locale and provides them via the getString + * appropriate for a particular locale and provides them via the getString * method.

    - * + * * The options object may contain any (or none) of the following properties: - * + * *

      *
    • locale - The locale of the strings to load. If not specified, the default - * locale is the the default for the web page or app in which the bundle is + * locale is the the default for the web page or app in which the bundle is * being loaded. - * + * *
    • name - Base name of the resource bundle to load. If not specified the default * base name is "resources". - * - *
    • type - Name the type of strings this bundle contains. Valid values are + * + *
    • type - Name the type of strings this bundle contains. Valid values are * "xml", "html", "text", "c", or "raw". The default is "text". If the type is "xml" or "html", - * then XML/HTML entities and tags are not pseudo-translated. During a real translation, + * then XML/HTML entities and tags are not pseudo-translated. During a real translation, * HTML character entities are translated to their corresponding characters in a source * string before looking that string up in the translations. Also, the characters "<", ">", * and "&" are converted to entities again in the output, but characters are left as they * are. If the type is "xml", "html", or "text" types, then the replacement parameter names - * are not pseudo-translated as well so that the output can be used for formatting with + * are not pseudo-translated as well so that the output can be used for formatting with * the IString class. If the type is "c" then all C language style printf replacement - * parameters (eg. "%s" and "%d") are skipped automatically. If the type is raw, all characters + * parameters (eg. "%s" and "%d") are skipped automatically. If the type is raw, all characters * are pseudo-translated, including replacement parameters as well as XML/HTML tags and entities. - * - *
    • lengthen - when pseudo-translating the string, tell whether or not to + * + *
    • lengthen - when pseudo-translating the string, tell whether or not to * automatically lengthen the string to simulate "long" languages such as German * or French. This is a boolean value. Default is false. - * + * *
    • missing - what to do when a resource is missing. The choices are: *
        *
      • source - return the source string unchanged *
      • pseudo - return the pseudo-translated source string, translated to the - * script of the locale if the mapping is available, or just the default Latin + * script of the locale if the mapping is available, or just the default Latin * pseudo-translation if not - *
      • empty - return the empty string + *
      • empty - return the empty string *
      * The default behaviour is the same as before, which is to return the source string * unchanged. - * - *
    • onLoad - a callback function to call when the resources are fully + * + *
    • onLoad - a callback function to call when the resources are fully * loaded. When the onLoad option is given, this class will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this - * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * callback can be used with preassembled or dynamic loading or a mix of the two. + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * - * The locale option may be given as a locale spec string or as an + * + * The locale option may be given as a locale spec string or as an * Locale object. If the locale option is not specified, then strings for - * the default locale will be loaded.

    - * + * the default locale will be loaded.

    + * * The name option can be used to put groups of strings together in a * single bundle. The strings will then appear together in a JS object in * a JS file that can be included before the ilib.

    - * + * * A resource bundle with a particular name is actually a set of bundles - * that are each specific to a language, a language plus a region, etc. + * that are each specific to a language, a language plus a region, etc. * All bundles with the same base name should - * contain the same set of source strings, but with different translations for - * the given locale. The user of the bundle does not need to be aware of - * the locale of the bundle, as long as it contains values for the strings + * contain the same set of source strings, but with different translations for + * the given locale. The user of the bundle does not need to be aware of + * the locale of the bundle, as long as it contains values for the strings * it needs.

    - * + * * Strings in bundles for a particular locale are inherited from parent bundles - * that are more generic. In general, the hierarchy is as follows (from + * that are more generic. In general, the hierarchy is as follows (from * least locale-specific to most locale-specific): - * + * *

      *
    1. language *
    2. region @@ -120,65 +120,65 @@ var IString = require("./IString.js"); *
    3. language_region_variant *
    4. language_script_region_variant *
    - * + * * That is, if the translation for a string does not exist in the current * locale, the more-generic parent locale is searched for the string. In the - * worst case scenario, the string is not found in the base locale's strings. + * worst case scenario, the string is not found in the base locale's strings. * In this case, the missing option guides this class on what to do. If - * the missing option is "source", then the original source is returned as + * the missing option is "source", then the original source is returned as * the translation. If it is "empty", the empty string is returned. If it * is "pseudo", then the pseudo-translated string that is appropriate for - * the default script of the locale is returned.

    - * + * the default script of the locale is returned.

    + * * This allows developers to create code with new or changed strings in it and check in that * code without waiting for the translations to be done first. The translated - * version of the app or web site will still function properly, but will show - * a spurious untranslated string here and there until the translations are - * done and also checked in.

    - * + * version of the app or web site will still function properly, but will show + * a spurious untranslated string here and there until the translations are + * done and also checked in.

    + * * The base is whatever language your developers use to code in. For - * a German web site, strings in the source code may be written in German + * a German web site, strings in the source code may be written in German * for example. Often this base is English, as many web sites are coded in * English, but that is not required.

    - * + * * The strings can be extracted with the ilib localization tool (which will be * shipped at some future time.) Once the strings * have been translated, the set of translated files can be generated with the * same tool. The output from the tool can be used as input to the ResBundle * object. It is up to the web page or app to make sure the JS file that defines * the bundle is included before creating the ResBundle instance.

    - * + * * A special locale "zxx-XX" is used as the pseudo-translation locale because - * zxx means "no linguistic information" in the ISO 639 standard, and the region - * code XX is defined to be user-defined in the ISO 3166 standard. + * zxx means "no linguistic information" in the ISO 639 standard, and the region + * code XX is defined to be user-defined in the ISO 3166 standard. * Pseudo-translation is a locale where the translations are generated on - * the fly based on the contents of the source string. Characters in the source - * string are replaced with other characters and returned. - * + * the fly based on the contents of the source string. Characters in the source + * string are replaced with other characters and returned. + * * Example. If the source string is: - * + * *

      * "This is a string"
      * 
    - * - * then the pseudo-translated version might look something like this: - * + * + * then the pseudo-translated version might look something like this: + * *
      * "Ţħïş ïş á şţřïñĝ"
      * 
    *

    - * + * * Pseudo-translation can be used to test that your app or web site is translatable - * before an actual translation has happened. These bugs can then be fixed + * before an actual translation has happened. These bugs can then be fixed * before the translation starts, avoiding an explosion of bugs later when - * each language's tester registers the same bug complaining that the same + * each language's tester registers the same bug complaining that the same * string is not translated. When pseudo-localizing with - * the Latin script, this allows the strings to be readable in the UI in the - * source language (if somewhat funky-looking), - * so that a tester can easily verify that the string is properly externalized + * the Latin script, this allows the strings to be readable in the UI in the + * source language (if somewhat funky-looking), + * so that a tester can easily verify that the string is properly externalized * and loaded from a resource bundle without the need to be able to read a - * foreign language.

    - * + * foreign language.

    + * * If one of a list of script tags is given in the pseudo-locale specifier, then the * pseudo-localization can map characters to very rough transliterations of * characters in the given script. For example, zxx-Hebr-XX maps strings to @@ -189,125 +189,125 @@ var IString = require("./IString.js"); * specified in the locale spec, or if the script is not supported, * then the default mapping maps Latin base characters to accented versions of * those Latin characters as in the example above. - * - * When the "lengthen" property is set to true in the options, the + * + * When the "lengthen" property is set to true in the options, the * pseudotranslation code will add digits to the end of the string to simulate - * the lengthening that occurs when translating to other languages. The above + * the lengthening that occurs when translating to other languages. The above * example will come out like this: - * + * *

      * "Ţħïş ïş á şţřïñĝ76543210"
      * 
    - * + * * The string is lengthened according to the length of the source string. If - * the source string is less than 20 characters long, the string is lengthened - * by 50%. If the source string is 20-40 + * the source string is less than 20 characters long, the string is lengthened + * by 50%. If the source string is 20-40 * characters long, the string is lengthened by 33%. If te string is greater * than 40 characters long, the string is lengthened by 20%.

    - * + * * The pseudotranslation always ends a string with the digit "0". If you do * not see the digit "0" in the UI for your app, you know that truncation - * has occurred, and the number you see at the end of the string tells you + * has occurred, and the number you see at the end of the string tells you * how many characters were truncated.

    - * - * + * + * * @constructor * @param {?Object} options Options controlling how the bundle is created */ var ResBundle = function (options) { - var lookupLocale, spec; - - this.locale = new Locale(); // use the default locale - this.baseName = "strings"; - this.type = "text"; - this.loadParams = {}; - this.missing = "source"; - this.sync = true; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? - new Locale(options.locale) : - options.locale; - } - if (options.name) { - this.baseName = options.name; - } - if (options.type) { - this.type = options.type; - } - this.lengthen = options.lengthen || false; - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - this.loadParams = options.loadParams; - } - if (typeof(options.missing) !== 'undefined') { - if (options.missing === "pseudo" || options.missing === "empty") { - this.missing = options.missing; - } - } - } else { - options = {sync: true}; - } - - this.map = {}; - - lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; - var object = "ResBundle-" + this.baseName; - - Utils.loadData({ - object: object, - locale: lookupLocale, - name: this.baseName + ".json", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (map) { - if (!map) { - map = ilib.data[this.baseName] || {}; - } - this.map = map; - if (this.locale.isPseudo()) { - this._loadPseudo(this.locale, options.onLoad); - } else if (this.missing === "pseudo") { - new LocaleInfo(this.locale, { - sync: this.sync, - loadParams: this.loadParams, - onLoad: ilib.bind(this, function (li) { - var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); - this._loadPseudo(pseudoLocale, options.onLoad); - }) - }); - } else { - if (typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } - }) - }); - - // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); - //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { - // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); - //} + var lookupLocale, spec; + + this.locale = new Locale(); // use the default locale + this.baseName = "strings"; + this.type = "text"; + this.loadParams = {}; + this.missing = "source"; + this.sync = true; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? + new Locale(options.locale) : + options.locale; + } + if (options.name) { + this.baseName = options.name; + } + if (options.type) { + this.type = options.type; + } + this.lengthen = options.lengthen || false; + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + this.loadParams = options.loadParams; + } + if (typeof(options.missing) !== 'undefined') { + if (options.missing === "pseudo" || options.missing === "empty") { + this.missing = options.missing; + } + } + } else { + options = {sync: true}; + } + + this.map = {}; + + lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; + var object = "ResBundle-" + this.baseName; + + Utils.loadData({ + object: object, + locale: lookupLocale, + name: this.baseName + ".json", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (map) { + if (!map) { + map = ilib.data[this.baseName] || {}; + } + this.map = map; + if (this.locale.isPseudo()) { + this._loadPseudo(this.locale, options.onLoad); + } else if (this.missing === "pseudo") { + new LocaleInfo(this.locale, { + sync: this.sync, + loadParams: this.loadParams, + onLoad: ilib.bind(this, function (li) { + var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); + this._loadPseudo(pseudoLocale, options.onLoad); + }) + }); + } else { + if (typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } + }) + }); + + // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); + //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { + // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); + //} }; ResBundle.defaultPseudo = ilib.data.pseudomap || { - "a": "à", - "e": "ë", - "i": "í", - "o": "õ", - "u": "ü", - "y": "ÿ", - "A": "Ã", - "E": "Ë", - "I": "Ï", - "O": "Ø", - "U": "Ú", - "Y": "Ŷ" + "a": "à", + "e": "ë", + "i": "í", + "o": "õ", + "u": "ü", + "y": "ÿ", + "A": "Ã", + "E": "Ë", + "I": "Ï", + "O": "Ø", + "U": "Ú", + "Y": "Ŷ" }; ResBundle.prototype = { @@ -315,167 +315,167 @@ ResBundle.prototype = { * @protected */ _loadPseudo: function (pseudoLocale, onLoad) { - Utils.loadData({ - object: "ResBundle", - locale: pseudoLocale, - name: "pseudomap.json", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (map) { - this.pseudomap = (!map || JSUtils.isEmpty(map)) ? ResBundle.defaultPseudo : map; - if (typeof(onLoad) === 'function') { - onLoad(this); - } - }) - }); + Utils.loadData({ + object: "ResBundle", + locale: pseudoLocale, + name: "pseudomap.json", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (map) { + this.pseudomap = (!map || JSUtils.isEmpty(map)) ? ResBundle.defaultPseudo : map; + if (typeof(onLoad) === 'function') { + onLoad(this); + } + }) + }); + }, + + /** + * Return the locale of this resource bundle. + * @return {Locale} the locale of this resource bundle object + */ + getLocale: function () { + return this.locale; + }, + + /** + * Return the name of this resource bundle. This corresponds to the name option + * given to the constructor. + * @return {string} name of the the current instance + */ + getName: function () { + return this.baseName; + }, + + /** + * Return the type of this resource bundle. This corresponds to the type option + * given to the constructor. + * @return {string} type of the the current instance + */ + getType: function () { + return this.type; + }, + + percentRE: new RegExp("%(\\d+\\$)?([\\-#\\+ 0,\\(])?(\\d+)?(\\.\\d+)?[bBhHsScCdoxXeEfgGaAtT%n]"), + + /** + * @private + * Pseudo-translate a string + */ + _pseudo: function (str) { + if (!str) { + return undefined; + } + var ret = "", i; + for (i = 0; i < str.length; i++) { + if (this.type !== "raw") { + if (this.type === "html" || this.type === "xml") { + if (str.charAt(i) === '<') { + ret += str.charAt(i++); + while (i < str.length && str.charAt(i) !== '>') { + ret += str.charAt(i++); + } + } else if (str.charAt(i) === '&') { + ret += str.charAt(i++); + while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { + ret += str.charAt(i++); + } + } else if (str.charAt(i) === '\\' && str.charAt(i+1) === "u") { + ret += str.substring(i, i+6); + i += 6; + } + } else if (this.type === "c") { + if (str.charAt(i) === "%") { + var m = this.percentRE.exec(str.substring(i)); + if (m && m.length) { + // console.log("Match found: " + JSON.stringify(m[0].replace("%", "%%"))); + ret += m[0]; + i += m[0].length; + } + } + + } + if (i < str.length) { + if (str.charAt(i) === '{') { + ret += str.charAt(i++); + while (i < str.length && str.charAt(i) !== '}') { + ret += str.charAt(i++); + } + if (i < str.length) { + ret += str.charAt(i); + } + } else { + ret += this.pseudomap[str.charAt(i)] || str.charAt(i); + } + } + } else { + ret += this.pseudomap[str.charAt(i)] || str.charAt(i); + } + } + if (this.lengthen) { + var add; + if (ret.length <= 20) { + add = Math.round(ret.length / 2); + } else if (ret.length > 20 && ret.length <= 40) { + add = Math.round(ret.length / 3); + } else { + add = Math.round(ret.length / 5); + } + for (i = add-1; i >= 0; i--) { + ret += (i % 10); + } + } + if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || + this.locale.getScript() === "Hani" || + this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || + this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { + // simulate Asian languages by getting rid of all the spaces + ret = ret.replace(/ /g, ""); + } + return ret; }, - - /** - * Return the locale of this resource bundle. - * @return {Locale} the locale of this resource bundle object - */ - getLocale: function () { - return this.locale; - }, - - /** - * Return the name of this resource bundle. This corresponds to the name option - * given to the constructor. - * @return {string} name of the the current instance - */ - getName: function () { - return this.baseName; - }, - - /** - * Return the type of this resource bundle. This corresponds to the type option - * given to the constructor. - * @return {string} type of the the current instance - */ - getType: function () { - return this.type; - }, - - percentRE: new RegExp("%(\\d+\\$)?([\\-#\\+ 0,\\(])?(\\d+)?(\\.\\d+)?[bBhHsScCdoxXeEfgGaAtT%n]"), - - /** - * @private - * Pseudo-translate a string - */ - _pseudo: function (str) { - if (!str) { - return undefined; - } - var ret = "", i; - for (i = 0; i < str.length; i++) { - if (this.type !== "raw") { - if (this.type === "html" || this.type === "xml") { - if (str.charAt(i) === '<') { - ret += str.charAt(i++); - while (i < str.length && str.charAt(i) !== '>') { - ret += str.charAt(i++); - } - } else if (str.charAt(i) === '&') { - ret += str.charAt(i++); - while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { - ret += str.charAt(i++); - } - } else if (str.charAt(i) === '\\' && str.charAt(i+1) === "u") { - ret += str.substring(i, i+6); - i += 6; - } - } else if (this.type === "c") { - if (str.charAt(i) === "%") { - var m = this.percentRE.exec(str.substring(i)); - if (m && m.length) { - // console.log("Match found: " + JSON.stringify(m[0].replace("%", "%%"))); - ret += m[0]; - i += m[0].length; - } - } - - } - if (i < str.length) { - if (str.charAt(i) === '{') { - ret += str.charAt(i++); - while (i < str.length && str.charAt(i) !== '}') { - ret += str.charAt(i++); - } - if (i < str.length) { - ret += str.charAt(i); - } - } else { - ret += this.pseudomap[str.charAt(i)] || str.charAt(i); - } - } - } else { - ret += this.pseudomap[str.charAt(i)] || str.charAt(i); - } - } - if (this.lengthen) { - var add; - if (ret.length <= 20) { - add = Math.round(ret.length / 2); - } else if (ret.length > 20 && ret.length <= 40) { - add = Math.round(ret.length / 3); - } else { - add = Math.round(ret.length / 5); - } - for (i = add-1; i >= 0; i--) { - ret += (i % 10); - } - } - if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || - this.locale.getScript() === "Hani" || - this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || - this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { - // simulate Asian languages by getting rid of all the spaces - ret = ret.replace(/ /g, ""); - } - return ret; - }, - - /** - * @private - * Escape html characters in the output. - */ - _escapeXml: function (str) { - str = str.replace(/&/g, '&'); - str = str.replace(//g, '>'); - return str; - }, - - /** - * @private - * @param {string} str the string to unescape - */ - _unescapeXml: function (str) { - str = str.replace(/&/g, '&'); - str = str.replace(/</g, '<'); - str = str.replace(/>/g, '>'); - return str; - }, - - /** - * @private - * Create a key name out of a source string. All this does so far is - * compress sequences of white space into a single space on the assumption - * that this doesn't really change the meaning of the string, and therefore - * all such strings that compress to the same thing should share the same - * translation. - * @param {null|string=} source the source string to make a key out of - */ - _makeKey: function (source) { - if (!source) return undefined; - var key = source.replace(/\s+/gm, ' '); - return (this.type === "xml" || this.type === "html") ? this._unescapeXml(key) : key; - }, - + + /** + * @private + * Escape html characters in the output. + */ + _escapeXml: function (str) { + str = str.replace(/&/g, '&'); + str = str.replace(//g, '>'); + return str; + }, + + /** + * @private + * @param {string} str the string to unescape + */ + _unescapeXml: function (str) { + str = str.replace(/&/g, '&'); + str = str.replace(/</g, '<'); + str = str.replace(/>/g, '>'); + return str; + }, + + /** + * @private + * Create a key name out of a source string. All this does so far is + * compress sequences of white space into a single space on the assumption + * that this doesn't really change the meaning of the string, and therefore + * all such strings that compress to the same thing should share the same + * translation. + * @param {null|string=} source the source string to make a key out of + */ + _makeKey: function (source) { + if (!source) return undefined; + var key = source.replace(/\s+/gm, ' '); + return (this.type === "xml" || this.type === "html") ? this._unescapeXml(key) : key; + }, + /** * @private */ - _getStringSingle: function(source, key, escapeMode) { + _getStringSingle: function(source, key, escapeMode) { if (!source && !key) return new IString(""); var trans; @@ -512,158 +512,158 @@ ResBundle.prototype = { ret.setLocale(this.locale.getSpec(), true, this.loadParams); // no callback return ret; } - }, - - /** - * Return a localized string, array, or object. This method can localize individual - * strings or arrays of strings.

    - * - * If the source parameter is a string, the translation of that string is looked - * up and returned. If the source parameter is an array of strings, then the translation - * of each of the elements of that array is looked up, and an array of translated strings - * is returned.

    - * - * If any string is not found in the loaded set of - * resources, the original source string is returned. If the key is not given, - * then the source string itself is used as the key. In the case where the - * source string is used as the key, the whitespace is compressed down to 1 space - * each, and the whitespace at the beginning and end of the string is trimmed.

    - * - * The escape mode specifies what type of output you are escaping the returned - * string for. Modes are similar to the types: - * - *

      - *
    • "html" -- prevents HTML injection by escaping the characters < > and & - *
    • "xml" -- currently same as "html" mode - *
    • "js" -- prevents breaking Javascript syntax by backslash escaping all quote and - * double-quote characters - *
    • "attribute" -- meant for HTML attribute values. Currently this is the same as - * "js" escape mode. - *
    • "default" -- use the type parameter from the constructor as the escape mode as well - *
    • "none" or undefined -- no escaping at all. - *
    - * - * The type parameter of the constructor specifies what type of strings this bundle - * is operating upon. This allows pseudo-translation and automatic key generation - * to happen properly by telling this class how to parse the string. The escape mode - * for this method is different in that it specifies how this string will be used in - * the calling code and therefore how to escape it properly.

    - * - * For example, a section of Javascript code may be constructing an HTML snippet in a - * string to add to the web page. In this case, the type parameter in the constructor should - * be "html" so that the source string can be parsed properly, but the escape mode should - * be "js" so that the output string can be used in Javascript without causing syntax - * errors. - * - * @param {?string|Array.=} source the source string or strings to translate - * @param {?string|Array.=} key optional name of the key, if any - * @param {?string=} escapeMode escape mode, if any - * @return {IString|Array.|undefined} the translation of the given source/key or undefined - * if the translation is not found and the source is undefined - */ - getString: function (source, key, escapeMode) { - if (!source && !key) return new IString(""); - - //if (typeof(source) === "object") { + }, + + /** + * Return a localized string, array, or object. This method can localize individual + * strings or arrays of strings.

    + * + * If the source parameter is a string, the translation of that string is looked + * up and returned. If the source parameter is an array of strings, then the translation + * of each of the elements of that array is looked up, and an array of translated strings + * is returned.

    + * + * If any string is not found in the loaded set of + * resources, the original source string is returned. If the key is not given, + * then the source string itself is used as the key. In the case where the + * source string is used as the key, the whitespace is compressed down to 1 space + * each, and the whitespace at the beginning and end of the string is trimmed.

    + * + * The escape mode specifies what type of output you are escaping the returned + * string for. Modes are similar to the types: + * + *

      + *
    • "html" -- prevents HTML injection by escaping the characters < > and & + *
    • "xml" -- currently same as "html" mode + *
    • "js" -- prevents breaking Javascript syntax by backslash escaping all quote and + * double-quote characters + *
    • "attribute" -- meant for HTML attribute values. Currently this is the same as + * "js" escape mode. + *
    • "default" -- use the type parameter from the constructor as the escape mode as well + *
    • "none" or undefined -- no escaping at all. + *
    + * + * The type parameter of the constructor specifies what type of strings this bundle + * is operating upon. This allows pseudo-translation and automatic key generation + * to happen properly by telling this class how to parse the string. The escape mode + * for this method is different in that it specifies how this string will be used in + * the calling code and therefore how to escape it properly.

    + * + * For example, a section of Javascript code may be constructing an HTML snippet in a + * string to add to the web page. In this case, the type parameter in the constructor should + * be "html" so that the source string can be parsed properly, but the escape mode should + * be "js" so that the output string can be used in Javascript without causing syntax + * errors. + * + * @param {?string|Array.=} source the source string or strings to translate + * @param {?string|Array.=} key optional name of the key, if any + * @param {?string=} escapeMode escape mode, if any + * @return {IString|Array.|undefined} the translation of the given source/key or undefined + * if the translation is not found and the source is undefined + */ + getString: function (source, key, escapeMode) { + if (!source && !key) return new IString(""); + + //if (typeof(source) === "object") { // TODO localize objects //} else - + if (ilib.isArray(source)) { - return source.map(ilib.bind(this, function(str) { - return typeof(str) === "string" ? this._getStringSingle(str, key, escapeMode) : str; - })); - } else { + return source.map(ilib.bind(this, function(str) { + return typeof(str) === "string" ? this._getStringSingle(str, key, escapeMode) : str; + })); + } else { return this._getStringSingle(source, key, escapeMode); - } - }, - - /** - * Return a localized string as an intrinsic Javascript String object. This does the same thing as - * the getString() method, but it returns a regular Javascript string instead of - * and IString instance. This means it cannot be formatted with the format() - * method without being wrapped in an IString instance first. - * - * @param {?string|Array.=} source the source string to translate - * @param {?string|Array.=} key optional name of the key, if any - * @param {?string=} escapeMode escape mode, if any - * @return {string|Array.|undefined} the translation of the given source/key or undefined - * if the translation is not found and the source is undefined - */ - getStringJS: function(source, key, escapeMode) { - if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { - return undefined; - } - //if (typeof(source) === "object") { - // TODO localize objects - //} else - - if (ilib.isArray(source)) { - return this.getString(source, key, escapeMode).map(function(str) { - return (str && str instanceof IString) ? str.toString() : str; - }); - } else { - var s = this.getString(source, key, escapeMode); + } + }, + + /** + * Return a localized string as an intrinsic Javascript String object. This does the same thing as + * the getString() method, but it returns a regular Javascript string instead of + * and IString instance. This means it cannot be formatted with the format() + * method without being wrapped in an IString instance first. + * + * @param {?string|Array.=} source the source string to translate + * @param {?string|Array.=} key optional name of the key, if any + * @param {?string=} escapeMode escape mode, if any + * @return {string|Array.|undefined} the translation of the given source/key or undefined + * if the translation is not found and the source is undefined + */ + getStringJS: function(source, key, escapeMode) { + if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { + return undefined; + } + //if (typeof(source) === "object") { + // TODO localize objects + //} else + + if (ilib.isArray(source)) { + return this.getString(source, key, escapeMode).map(function(str) { + return (str && str instanceof IString) ? str.toString() : str; + }); + } else { + var s = this.getString(source, key, escapeMode); return s ? s.toString() : undefined; - } - }, - - /** - * Return true if the current bundle contains a translation for the given key and - * source. The - * getString method will always return a string for any given key and source - * combination, so it cannot be used to tell if a translation exists. Either one - * or both of the source and key must be specified. If both are not specified, - * this method will return false. - * - * @param {?string=} source source string to look up - * @param {?string=} key key to look up - * @return {boolean} true if this bundle contains a translation for the key, and - * false otherwise - */ - containsKey: function(source, key) { - if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { - return false; - } - - var keyName = key || this._makeKey(source); - return typeof(this.map[keyName]) !== 'undefined'; - }, - - /** - * Return the merged resources as an entire object. When loading resources for a - * locale that are not just a set of translated strings, but instead an entire - * structured javascript object, you can gain access to that object via this call. This method - * will ensure that all the of the parts of the object are correct for the locale.

    - * - * For pre-assembled data, it starts by loading ilib.data[name], where - * name is the base name for this set of resources. Then, it successively - * merges objects in the base data using progressively more locale-specific data. - * It loads it in this order from ilib.data: - * - *

      - *
    1. language - *
    2. region - *
    3. language_script - *
    4. language_region - *
    5. region_variant - *
    6. language_script_region - *
    7. language_region_variant - *
    8. language_script_region_variant - *
    - * - * For dynamically loaded data, the code attempts to load the same sequence as - * above, but with slash path separators instead of underscores.

    - * - * Loading the resources this way allows the program to share resources between all - * locales that share a common language, region, or script. As a - * general rule-of-thumb, resources should be as generic as possible in order to - * cover as many locales as possible. - * - * @return {Object} returns the object that is the basis for this resources instance - */ - getResObj: function () { - return this.map; - } + } + }, + + /** + * Return true if the current bundle contains a translation for the given key and + * source. The + * getString method will always return a string for any given key and source + * combination, so it cannot be used to tell if a translation exists. Either one + * or both of the source and key must be specified. If both are not specified, + * this method will return false. + * + * @param {?string=} source source string to look up + * @param {?string=} key key to look up + * @return {boolean} true if this bundle contains a translation for the key, and + * false otherwise + */ + containsKey: function(source, key) { + if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { + return false; + } + + var keyName = key || this._makeKey(source); + return typeof(this.map[keyName]) !== 'undefined'; + }, + + /** + * Return the merged resources as an entire object. When loading resources for a + * locale that are not just a set of translated strings, but instead an entire + * structured javascript object, you can gain access to that object via this call. This method + * will ensure that all the of the parts of the object are correct for the locale.

    + * + * For pre-assembled data, it starts by loading ilib.data[name], where + * name is the base name for this set of resources. Then, it successively + * merges objects in the base data using progressively more locale-specific data. + * It loads it in this order from ilib.data: + * + *

      + *
    1. language + *
    2. region + *
    3. language_script + *
    4. language_region + *
    5. region_variant + *
    6. language_script_region + *
    7. language_region_variant + *
    8. language_script_region_variant + *
    + * + * For dynamically loaded data, the code attempts to load the same sequence as + * above, but with slash path separators instead of underscores.

    + * + * Loading the resources this way allows the program to share resources between all + * locales that share a common language, region, or script. As a + * general rule-of-thumb, resources should be as generic as possible in order to + * cover as many locales as possible. + * + * @return {Object} returns the object that is the basis for this resources instance + */ + getResObj: function () { + return this.map; + } }; module.exports = ResBundle; diff --git a/js/lib/RhinoLoader.js b/js/lib/RhinoLoader.js index a88fb06914..43e539bedd 100644 --- a/js/lib/RhinoLoader.js +++ b/js/lib/RhinoLoader.js @@ -1,6 +1,6 @@ /* - * RhinoLoader.js - loader implementation for Rhino-based apps. - * + * RhinoLoader.js - loader implementation for Rhino-based apps. + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,37 +26,37 @@ var Loader = require("./Loader.js"); /** * @class * An implementation of the Loader class for Rhino. - * + * * @private * @constructor */ var RhinoLoader = function() { - //console.log("new RhinoLoader instance called with " + fr); - - this.parent.call(this, ilib); - - this.root = module.resolve("..") || environment["user.dir"]; - this.root = this.root.replace("file://", ""); - // console.log("RhinoLoader using root: " + this.root); - - if (this.root[this.root.length-1] === '/') { - this.root = this.root.substring(0, this.root.length-1); - } + //console.log("new RhinoLoader instance called with " + fr); + + this.parent.call(this, ilib); + + this.root = module.resolve("..") || environment["user.dir"]; + this.root = this.root.replace("file://", ""); + // console.log("RhinoLoader using root: " + this.root); + + if (this.root[this.root.length-1] === '/') { + this.root = this.root.substring(0, this.root.length-1); + } + + this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first + + // then a standard locale dir of a built version of ilib from npm + this._exists(path.join(this.root, "locale"), "localeinfo.json"); + + // try the standard install directories + // this won't work under a web server because /usr is out + // of the context that the web app is allowed to access + //this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); + + // ... else fall back to see if we're in a check-out dir of ilib + // this._exists(path.join(this.root, "data", "locale"), "localeinfo.json"); - this.includePath.push(path.join(this.root, "resources")); // always check the application's resources dir first - - // then a standard locale dir of a built version of ilib from npm - this._exists(path.join(this.root, "locale"), "localeinfo.json"); - - // try the standard install directories - // this won't work under a web server because /usr is out - // of the context that the web app is allowed to access - //this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); - - // ... else fall back to see if we're in a check-out dir of ilib - // this._exists(path.join(this.root, "data", "locale"), "localeinfo.json"); - - //console.log("RhinoLoader: include path is now " + JSON.stringify(this.includePath)); + //console.log("RhinoLoader: include path is now " + JSON.stringify(this.includePath)); }; RhinoLoader.prototype = new Loader(); @@ -64,44 +64,44 @@ RhinoLoader.prototype.parent = Loader; RhinoLoader.prototype.constructor = RhinoLoader; RhinoLoader.prototype._loadFile = function (pathname, sync, cb) { - // ignore sync flag -- always load synchronously - // console.log("RhinoLoader._loadFile: attempting to load " + pathname); - var text = ""; - var reader; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(pathname), "utf-8")); - var tmp; - while ((tmp = reader.readLine()) !== null) { - text += tmp + '\n'; - } - } catch (e) { - // ignore - text = undefined; - } finally { - if (reader) { - try { - reader.close(); - } catch (e2) {} - } - cb && typeof(cb) === 'function' && cb(text); - } - return text; + // ignore sync flag -- always load synchronously + // console.log("RhinoLoader._loadFile: attempting to load " + pathname); + var text = ""; + var reader; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(pathname), "utf-8")); + var tmp; + while ((tmp = reader.readLine()) !== null) { + text += tmp + '\n'; + } + } catch (e) { + // ignore + text = undefined; + } finally { + if (reader) { + try { + reader.close(); + } catch (e2) {} + } + cb && typeof(cb) === 'function' && cb(text); + } + return text; }; RhinoLoader.prototype._exists = function(dir, file) { - var fullpath = path.normalize(path.join(dir, file)); - // console.log("RhinoLoader._exists: checking for the existence of " + fullpath); - try { - var f = new File(fullpath); - if (f.exists() && f.canRead()) { - // console.log("RhinoLoader._exists: found"); - this.includePath.push(dir); - } - } catch (e) { - // ignore errors -- that means we have a permission problem and shouldn't add - // the dir to the include path anyways - console.log(e); - } + var fullpath = path.normalize(path.join(dir, file)); + // console.log("RhinoLoader._exists: checking for the existence of " + fullpath); + try { + var f = new File(fullpath); + if (f.exists() && f.canRead()) { + // console.log("RhinoLoader._exists: found"); + this.includePath.push(dir); + } + } catch (e) { + // ignore errors -- that means we have a permission problem and shouldn't add + // the dir to the include path anyways + console.log(e); + } }; module.exports = RhinoLoader; \ No newline at end of file diff --git a/js/lib/ScriptInfo.js b/js/lib/ScriptInfo.js index a01080b2c8..da8f9fd182 100644 --- a/js/lib/ScriptInfo.js +++ b/js/lib/ScriptInfo.js @@ -1,6 +1,6 @@ /* * ScriptInfo.js - information about scripts - * + * * Copyright © 2012-2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,98 +19,98 @@ // !data scripts -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); /** * @class * Create a new script info instance. This class encodes information about * scripts, which are sets of characters used in a writing system.

    - * + * * The options object may contain any of the following properties: - * + * *

      - *
    • onLoad - a callback function to call when the script info object is fully + *
    • onLoad - a callback function to call when the script info object is fully * loaded. When the onLoad option is given, the script info object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * - * + * + * * @constructor * @param {string} script The ISO 15924 4-letter identifier for the script - * @param {Object=} options parameters to initialize this instance + * @param {Object=} options parameters to initialize this instance */ var ScriptInfo = function(script, options) { - var sync = true, - loadParams = undefined; - - this.script = script; - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - loadParams = options.loadParams; - } - } - - if (!ilib.data.scripts) { - Utils.loadData({ - object: "ScriptInfo", - nonlocale: true, - name: "scripts.json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - if (!info) { - info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; - } - ilib.data.scripts = info; - this.info = script && ilib.data.scripts[script]; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - this.info = ilib.data.scripts[script]; + var sync = true, + loadParams = undefined; + + this.script = script; + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + loadParams = options.loadParams; + } + } + + if (!ilib.data.scripts) { + Utils.loadData({ + object: "ScriptInfo", + nonlocale: true, + name: "scripts.json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + if (!info) { + info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; + } + ilib.data.scripts = info; + this.info = script && ilib.data.scripts[script]; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + this.info = ilib.data.scripts[script]; if (options && typeof(options.onLoad) === 'function') { options.onLoad(this); } - } + } }; /** * @private */ ScriptInfo._getScriptsArray = function() { - var ret = [], - script = undefined, - scripts = ilib.data.scripts; - - for (script in scripts) { - if (script && scripts[script]) { - ret.push(script); - } - } - - return ret; + var ret = [], + script = undefined, + scripts = ilib.data.scripts; + + for (script in scripts) { + if (script && scripts[script]) { + ret.push(script); + } + } + + return ret; }; /** @@ -124,98 +124,98 @@ ScriptInfo._getScriptsArray = function() { * ilib knows about */ ScriptInfo.getAllScripts = function(sync, loadParams, onLoad) { - if (!ilib.data.scripts) { - Utils.loadData({ - object: "ScriptInfo", - locale: "-", - name: "scripts.json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - ilib.data.scripts = info; - - if (typeof(onLoad) === 'function') { - onLoad(ScriptInfo._getScriptsArray()); - } - }) - }); - } else { + if (!ilib.data.scripts) { + Utils.loadData({ + object: "ScriptInfo", + locale: "-", + name: "scripts.json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + ilib.data.scripts = info; + + if (typeof(onLoad) === 'function') { + onLoad(ScriptInfo._getScriptsArray()); + } + }) + }); + } else { if (typeof(onLoad) === 'function') { onLoad(ScriptInfo._getScriptsArray()); } - } - - return ScriptInfo._getScriptsArray(); + } + + return ScriptInfo._getScriptsArray(); }; ScriptInfo.prototype = { - /** - * Return the 4-letter ISO 15924 identifier associated - * with this script. - * @return {string} the 4-letter ISO code for this script - */ - getCode: function () { - return this.info && this.script; - }, - - /** - * Get the ISO 15924 code number associated with this - * script. - * - * @return {number} the ISO 15924 code number - */ - getCodeNumber: function () { - return this.info && this.info.nb || 0; - }, - - /** - * Get the name of this script in English. - * - * @return {string} the name of this script in English - */ - getName: function () { - return this.info && this.info.nm; - }, - - /** - * Get the long identifier assciated with this script. - * - * @return {string} the long identifier of this script - */ - getLongCode: function () { - return this.info && this.info.lid; - }, - - /** - * Return the usual direction that text in this script is written - * in. Possible return values are "rtl" for right-to-left, - * "ltr" for left-to-right, and "ttb" for top-to-bottom. - * - * @return {string} the usual direction that text in this script is - * written in - */ - getScriptDirection: function() { - return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; - }, - - /** - * Return true if this script typically requires an input method engine - * to enter its characters. - * - * @return {boolean} true if this script typically requires an IME - */ - getNeedsIME: function () { - return this.info && this.info.ime ? true : false; // converts undefined to false - }, - - /** - * Return true if this script uses lower- and upper-case characters. - * - * @return {boolean} true if this script uses letter case - */ - getCasing: function () { - return this.info && this.info.casing ? true : false; // converts undefined to false - } + /** + * Return the 4-letter ISO 15924 identifier associated + * with this script. + * @return {string} the 4-letter ISO code for this script + */ + getCode: function () { + return this.info && this.script; + }, + + /** + * Get the ISO 15924 code number associated with this + * script. + * + * @return {number} the ISO 15924 code number + */ + getCodeNumber: function () { + return this.info && this.info.nb || 0; + }, + + /** + * Get the name of this script in English. + * + * @return {string} the name of this script in English + */ + getName: function () { + return this.info && this.info.nm; + }, + + /** + * Get the long identifier assciated with this script. + * + * @return {string} the long identifier of this script + */ + getLongCode: function () { + return this.info && this.info.lid; + }, + + /** + * Return the usual direction that text in this script is written + * in. Possible return values are "rtl" for right-to-left, + * "ltr" for left-to-right, and "ttb" for top-to-bottom. + * + * @return {string} the usual direction that text in this script is + * written in + */ + getScriptDirection: function() { + return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; + }, + + /** + * Return true if this script typically requires an input method engine + * to enter its characters. + * + * @return {boolean} true if this script typically requires an IME + */ + getNeedsIME: function () { + return this.info && this.info.ime ? true : false; // converts undefined to false + }, + + /** + * Return true if this script uses lower- and upper-case characters. + * + * @return {boolean} true if this script uses letter case + */ + getCasing: function () { + return this.info && this.info.casing ? true : false; // converts undefined to false + } }; module.exports = ScriptInfo; \ No newline at end of file diff --git a/js/lib/SearchUtils.js b/js/lib/SearchUtils.js index 4a6e5e3566..0f4c9be16d 100644 --- a/js/lib/SearchUtils.js +++ b/js/lib/SearchUtils.js @@ -1,6 +1,6 @@ /* * SearchUtils.js - Misc search utility routines - * + * * Copyright © 2013-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,111 +21,111 @@ var SearchUtils = {}; /** * Binary search a sorted array for a particular target value. - * If the exact value is not found, it returns the index of the smallest - * entry that is greater than the given target value.

    - * + * If the exact value is not found, it returns the index of the smallest + * entry that is greater than the given target value.

    + * * The comparator - * parameter is a function that knows how to compare elements of the + * parameter is a function that knows how to compare elements of the * array and the target. The function should return a value greater than 0 * if the array element is greater than the target, a value less than 0 if - * the array element is less than the target, and 0 if the array element + * the array element is less than the target, and 0 if the array element * and the target are equivalent.

    - * + * * If the comparator function is not specified, this function assumes - * the array and the target are numeric values and should be compared + * the array and the target are numeric values and should be compared * as such.

    - * - * + * + * * @static - * @param {*} target element being sought + * @param {*} target element being sought * @param {Array} arr the array being searched * @param {?function(*,*)=} comparator a comparator that is appropriate for comparing two entries - * in the array - * @return the index of the array into which the value would fit if - * inserted, or -1 if given array is not an array or the target is not + * in the array + * @return the index of the array into which the value would fit if + * inserted, or -1 if given array is not an array or the target is not * a number */ SearchUtils.bsearch = function(target, arr, comparator) { - if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { - return -1; - } - - var high = arr.length - 1, - low = 0, - mid = 0, - value, - cmp = comparator || SearchUtils.bsearch.numbers; - - while (low <= high) { - mid = Math.floor((high+low)/2); - value = cmp(arr[mid], target); - if (value > 0) { - high = mid - 1; - } else if (value < 0) { - low = mid + 1; - } else { - return mid; - } - } - - return low; + if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { + return -1; + } + + var high = arr.length - 1, + low = 0, + mid = 0, + value, + cmp = comparator || SearchUtils.bsearch.numbers; + + while (low <= high) { + mid = Math.floor((high+low)/2); + value = cmp(arr[mid], target); + if (value > 0) { + high = mid - 1; + } else if (value < 0) { + low = mid + 1; + } else { + return mid; + } + } + + return low; }; /** * Returns whether or not the given element is greater than, less than, * or equal to the given target.

    - * + * * @private * @static * @param {number} element the element being tested * @param {number} target the target being sought */ SearchUtils.bsearch.numbers = function(element, target) { - return element - target; + return element - target; }; /** - * Do a bisection search of a function for a particular target value.

    - * - * The function to search is a function that takes a numeric parameter, - * does calculations, and returns gives a numeric result. The - * function should should be smooth and not have any discontinuities + * Do a bisection search of a function for a particular target value.

    + * + * The function to search is a function that takes a numeric parameter, + * does calculations, and returns gives a numeric result. The + * function should should be smooth and not have any discontinuities * between the low and high values of the parameter. - * - * + * + * * @static * @param {number} target value being sought * @param {number} low the lower bounds to start searching * @param {number} high the upper bounds to start searching * @param {number} precision minimum precision to support. Use 0 if you want to use the default. - * @param {?function(number)=} func function to search + * @param {?function(number)=} func function to search * @return an approximation of the input value to the function that gives the desired - * target output value, correct to within the error range of Javascript floating point + * target output value, correct to within the error range of Javascript floating point * arithmetic, or NaN if there was some error */ SearchUtils.bisectionSearch = function(target, low, high, precision, func) { - if (typeof(target) !== 'number' || - typeof(low) !== 'number' || - typeof(high) !== 'number' || - typeof(func) !== 'function') { - return NaN; - } - - var mid = 0, - value, - pre = precision > 0 ? precision : 1e-13; - - do { - mid = (high+low)/2; - value = func(mid); - if (value > target) { - high = mid; - } else if (value < target) { - low = mid; - } - } while (high - low > pre); - - return mid; + if (typeof(target) !== 'number' || + typeof(low) !== 'number' || + typeof(high) !== 'number' || + typeof(func) !== 'function') { + return NaN; + } + + var mid = 0, + value, + pre = precision > 0 ? precision : 1e-13; + + do { + mid = (high+low)/2; + value = func(mid); + if (value > target) { + high = mid; + } else if (value < target) { + low = mid; + } + } while (high - low > pre); + + return mid; }; module.exports = SearchUtils; diff --git a/js/lib/StringMapper.js b/js/lib/StringMapper.js index 870490984b..9e21a12d5d 100644 --- a/js/lib/StringMapper.js +++ b/js/lib/StringMapper.js @@ -1,6 +1,6 @@ /* * StringMapper.js - ilib string mapper class definition - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); @@ -26,145 +26,145 @@ var IString = require("./IString.js"); /** * @class * Create a new string mapper instance.

    - * + * * The options may contain any of the following properties: - * + * *

      - *
    • locale - locale to use when loading the mapper. Some maps are + *
    • locale - locale to use when loading the mapper. Some maps are * locale-dependent, and this locale selects the right one. Default if this is * not specified is the current locale. - * + * *
    • name - the name of the map to load - * - *
    • mapFunction - specify an algorithmic mapping function to use if + * + *
    • mapFunction - specify an algorithmic mapping function to use if * the mapper does not have an explicit mapping for a character. The idea is - * to save disk and memory when algorithmic mapping can be done for some of + * to save disk and memory when algorithmic mapping can be done for some of * the characters, but not others. The exceptions can go into the json file, * and the characters that conform to the rule can be mapped algorithmically. * The map function should take a string containing 1 character as a parameter * and should return a string containing one or more characters. If the * character is outside of the range that can be mapped, it should be returned - * unchanged. - * - *
    • onLoad - a callback function to call when this object is fully + * unchanged. + * + *
    • onLoad - a callback function to call when this object is fully * loaded. When the onLoad option is given, this object will attempt to * load any missing locale data using the ilib loader callback. - * When the constructor is done (even if the data is already preassembled), the + * When the constructor is done (even if the data is already preassembled), the * onLoad function is called with the current instance as a parameter, so this * callback can be used with preassembled or dynamic loading or a mix of the two. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will - * not be usable for a while. + * not be usable for a while. * - *
    • loadParams - an object containing parameters to pass to the + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * - * + * + * * @constructor - * @param {Object=} options options to initialize this string mapper + * @param {Object=} options options to initialize this string mapper */ var StringMapper = function (options) { - var sync = true, - loadParams = undefined; - - this.locale = new Locale(); - this.mapData = {}; - this.mapFunction = undefined; - - if (options) { - if (typeof(options.locale) !== 'undefined') { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (typeof(options.name) !== 'undefined') { - this.name = options.name; - } - - if (typeof(options.mapFunction) === 'function') { - this.mapFunction = options.mapFunction; - } - - if (typeof(options.sync) !== 'undefined') { - sync = (options.sync == true); - } - - if (typeof(options.loadParams) !== 'undefined') { - loadParams = options.loadParams; - } - } - - Utils.loadData({ - object: "StringMapper", - locale: this.locale, - name: this.name + ".json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (map) { - this.mapData = map || {}; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + var sync = true, + loadParams = undefined; + + this.locale = new Locale(); + this.mapData = {}; + this.mapFunction = undefined; + + if (options) { + if (typeof(options.locale) !== 'undefined') { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (typeof(options.name) !== 'undefined') { + this.name = options.name; + } + + if (typeof(options.mapFunction) === 'function') { + this.mapFunction = options.mapFunction; + } + + if (typeof(options.sync) !== 'undefined') { + sync = (options.sync == true); + } + + if (typeof(options.loadParams) !== 'undefined') { + loadParams = options.loadParams; + } + } + + Utils.loadData({ + object: "StringMapper", + locale: this.locale, + name: this.name + ".json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (map) { + this.mapData = map || {}; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; StringMapper.prototype = { - /** - * Return the locale that this mapper was constructed. - * @returns - */ - getLocale: function () { - return this.locale; - }, - - getName: function () { - return this.name; - }, - - /** - * Map a string using the mapping defined in the constructor. This method - * iterates through all characters in the string and maps them one-by-one. - * If a particular character has a mapping, the mapping result will be - * added to the output. If there is no mapping, but there is a mapFunction - * defined, the mapFunction results will be added to the result. Otherwise, - * the original character from the input string will be added to the result. - * - * @param {string|IString|undefined} string - * @return {string|IString|undefined} - */ - map: function (string) { - var input; - if (!string) { - return string; - } - if (typeof(string) === 'string') { - input = new IString(string); - } else { - input = string.toString(); - } - var ret = ""; - var it = input.charIterator(); - var c; - - while (it.hasNext()) { - c = it.next(); - if (this.mapData && this.mapData[c]) { - ret += this.mapData[c]; - } else if (this.mapFunction) { - ret += this.mapFunction(c); - } else { - ret += c; - } - } - - return ret; - } + /** + * Return the locale that this mapper was constructed. + * @returns + */ + getLocale: function () { + return this.locale; + }, + + getName: function () { + return this.name; + }, + + /** + * Map a string using the mapping defined in the constructor. This method + * iterates through all characters in the string and maps them one-by-one. + * If a particular character has a mapping, the mapping result will be + * added to the output. If there is no mapping, but there is a mapFunction + * defined, the mapFunction results will be added to the result. Otherwise, + * the original character from the input string will be added to the result. + * + * @param {string|IString|undefined} string + * @return {string|IString|undefined} + */ + map: function (string) { + var input; + if (!string) { + return string; + } + if (typeof(string) === 'string') { + input = new IString(string); + } else { + input = string.toString(); + } + var ret = ""; + var it = input.charIterator(); + var c; + + while (it.hasNext()) { + c = it.next(); + if (this.mapData && this.mapData[c]) { + ret += this.mapData[c]; + } else if (this.mapFunction) { + ret += this.mapFunction(c); + } else { + ret += c; + } + } + + return ret; + } }; module.exports = StringMapper; \ No newline at end of file diff --git a/js/lib/TemperatureUnit.js b/js/lib/TemperatureUnit.js index 3ec78e9844..9e94b3a130 100644 --- a/js/lib/TemperatureUnit.js +++ b/js/lib/TemperatureUnit.js @@ -69,7 +69,7 @@ TemperatureUnit.ratios = { * @return {string} the name of the type of this measurement */ TemperatureUnit.prototype.getMeasure = function() { - return "temperature"; + return "temperature"; }; /** diff --git a/js/lib/ThaiSolarCal.js b/js/lib/ThaiSolarCal.js index 9853ba47da..e305ce1a14 100644 --- a/js/lib/ThaiSolarCal.js +++ b/js/lib/ThaiSolarCal.js @@ -31,8 +31,8 @@ var GregorianCal = require("./GregorianCal.js"); * @extends Calendar */ var ThaiSolarCal = function(options) { - this.type = "thaisolar"; - + this.type = "thaisolar"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -49,10 +49,10 @@ ThaiSolarCal.prototype.constructor = ThaiSolarCal; * @return {boolean} true if the given year is a leap year */ ThaiSolarCal.prototype.isLeapYear = function(year) { - var y = (typeof(year) === 'number' ? year : year.getYears()); - y -= 543; - var centuries = MathUtils.mod(y, 400); - return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); + var y = (typeof(year) === 'number' ? year : year.getYears()); + y -= 543; + var centuries = MathUtils.mod(y, 400); + return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); }; diff --git a/js/lib/ThaiSolarDate.js b/js/lib/ThaiSolarDate.js index eeb34b1df6..e2f1f15040 100644 --- a/js/lib/ThaiSolarDate.js +++ b/js/lib/ThaiSolarDate.js @@ -1,6 +1,6 @@ /* * ThaiSolarDate.js - Represent a date in the ThaiSolar calendar - * + * * Copyright © 2013-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var JSUtils = require("./JSUtils.js"); var IDate = require("./IDate.js"); @@ -27,62 +27,62 @@ var GregorianDate = require("./GregorianDate.js"); /** * @class - * Construct a new Thai solar date object. The constructor parameters can + * Construct a new Thai solar date object. The constructor parameters can * contain any of the following properties: - * + * *
      - *
    • unixtime - sets the time of this instance according to the given + *
    • unixtime - sets the time of this instance according to the given * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. - * + * *
    • julianday - sets the time of this instance according to the given * Julian Day instance or the Julian Day given as a float - * + * *
    • year - any integer, including 0 - * + * *
    • month - 1 to 12, where 1 means January, 2 means February, etc. - * + * *
    • day - 1 to 31 - * - *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation + * + *
    • hour - 0 to 23. A formatter is used to display 12 hour clocks, but this representation * is always done with an unambiguous 24 hour representation - * + * *
    • minute - 0 to 59 - * + * *
    • second - 0 to 59 - * + * *
    • millisecond - 0 to 999 - * - *
    • timezone - the TimeZone instance or time zone name as a string + * + *
    • timezone - the TimeZone instance or time zone name as a string * of this Thai solar date. The date/time is kept in the local time. The time zone * is used later if this date is formatted according to a different time zone and * the difference has to be calculated, or when the date format has a time zone * component in it. - * - *
    • locale - locale for this Thai solar date. If the time zone is not + * + *
    • locale - locale for this Thai solar date. If the time zone is not * given, it can be inferred from this locale. For locales that span multiple - * time zones, the one with the largest population is chosen as the one that - * represents the locale. + * time zones, the one with the largest population is chosen as the one that + * represents the locale. *
    * * If the constructor is called with another Thai solar date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.

    - * - * If the constructor is called with no arguments at all or if none of the - * properties listed above - * from unixtime through millisecond are present, then the date - * components are + * + * If the constructor is called with no arguments at all or if none of the + * properties listed above + * from unixtime through millisecond are present, then the date + * components are * filled in with the current date at the time of instantiation. Note that if - * you do not give the time zone when defaulting to the current time and the + * you do not give the time zone when defaulting to the current time and the * time zone for all of ilib was not set with ilib.setTimeZone(), then the - * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich + * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich * Mean Time").

    - * + * * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).

    - * - * + * + * * @constructor * @extends GregorianDate * @param {Object=} params parameters that govern the settings and behaviour of this Thai solar date @@ -92,7 +92,7 @@ var ThaiSolarDate = function(params) { if (params) { JSUtils.shallowCopy(params, p); - + // there is 198327 days difference between the Thai solar and // Gregorian epochs which is equivalent to 543 years if (typeof(p.year) !== 'undefined') { @@ -108,7 +108,7 @@ var ThaiSolarDate = function(params) { p.onLoad = ilib.bind(this, function(gd) { this.cal = new ThaiSolarCal(); - + // make sure the year is set correctly from the original params if (params && typeof(params.year) !== 'undefined') { this.year = parseInt(params.year, 10); @@ -128,7 +128,7 @@ ThaiSolarDate.prototype.constructor = ThaiSolarDate; /** * the difference between a zero Julian day and the zero Thai Solar date. - * This is some 543 years before the start of the Gregorian epoch. + * This is some 543 years before the start of the Gregorian epoch. * @private * @type number */ @@ -139,92 +139,92 @@ ThaiSolarDate.epoch = 1523097.5; * @protected */ ThaiSolarDate.prototype._calcDateComponents = function () { - // there is 198327 days difference between the Thai solar and - // Gregorian epochs which is equivalent to 543 years - // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); - this.parent._calcDateComponents.call(this); - this.year += 543; + // there is 198327 days difference between the Thai solar and + // Gregorian epochs which is equivalent to 543 years + // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); + this.parent._calcDateComponents.call(this); + this.year += 543; }; /** * Return the Rata Die (fixed day) number of this date. - * + * * @protected * @return {number} the rd date as a number */ ThaiSolarDate.prototype.getRataDie = function() { - // there is 198327 days difference between the Thai solar and - // Gregorian epochs which is equivalent to 543 years - return this.rd.getRataDie() + 198327; + // there is 198327 days difference between the Thai solar and + // Gregorian epochs which is equivalent to 543 years + return this.rd.getRataDie() + 198327; }; /** - * Return a new Gregorian date instance that represents the first instance of the + * Return a new Gregorian date instance that represents the first instance of the * given day of the week before the current date. The day of the week is encoded * as a number where 0 = Sunday, 1 = Monday, etc. - * + * * @param {number} dow the day of the week before the current date that is being sought * @return {IDate} the date being sought */ ThaiSolarDate.prototype.before = function (dow) { - return new ThaiSolarDate({ - rd: this.rd.before(dow, this.offset) + 198327, - timezone: this.timezone - }); + return new ThaiSolarDate({ + rd: this.rd.before(dow, this.offset) + 198327, + timezone: this.timezone + }); }; /** - * Return a new Gregorian date instance that represents the first instance of the + * Return a new Gregorian date instance that represents the first instance of the * given day of the week after the current date. The day of the week is encoded * as a number where 0 = Sunday, 1 = Monday, etc. - * + * * @param {number} dow the day of the week after the current date that is being sought * @return {IDate} the date being sought */ ThaiSolarDate.prototype.after = function (dow) { - return new ThaiSolarDate({ - rd: this.rd.after(dow, this.offset) + 198327, - timezone: this.timezone - }); + return new ThaiSolarDate({ + rd: this.rd.after(dow, this.offset) + 198327, + timezone: this.timezone + }); }; /** - * Return a new Gregorian date instance that represents the first instance of the + * Return a new Gregorian date instance that represents the first instance of the * given day of the week on or before the current date. The day of the week is encoded * as a number where 0 = Sunday, 1 = Monday, etc. - * + * * @param {number} dow the day of the week on or before the current date that is being sought * @return {IDate} the date being sought */ ThaiSolarDate.prototype.onOrBefore = function (dow) { - return new ThaiSolarDate({ - rd: this.rd.onOrBefore(dow, this.offset) + 198327, - timezone: this.timezone - }); + return new ThaiSolarDate({ + rd: this.rd.onOrBefore(dow, this.offset) + 198327, + timezone: this.timezone + }); }; /** - * Return a new Gregorian date instance that represents the first instance of the + * Return a new Gregorian date instance that represents the first instance of the * given day of the week on or after the current date. The day of the week is encoded * as a number where 0 = Sunday, 1 = Monday, etc. - * + * * @param {number} dow the day of the week on or after the current date that is being sought * @return {IDate} the date being sought */ ThaiSolarDate.prototype.onOrAfter = function (dow) { - return new ThaiSolarDate({ - rd: this.rd.onOrAfter(dow, this.offset) + 198327, - timezone: this.timezone - }); + return new ThaiSolarDate({ + rd: this.rd.onOrAfter(dow, this.offset) + 198327, + timezone: this.timezone + }); }; /** * Return the name of the calendar that governs this date. - * + * * @return {string} a string giving the name of the calendar */ ThaiSolarDate.prototype.getCalendar = function() { - return "thaisolar"; + return "thaisolar"; }; //register with the factory method diff --git a/js/lib/TimeZone.js b/js/lib/TimeZone.js index 69d98c016b..fbf40212c6 100644 --- a/js/lib/TimeZone.js +++ b/js/lib/TimeZone.js @@ -1,6 +1,6 @@ /* * TimeZone.js - Definition of a time zone class - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data localeinfo zoneinfo -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var MathUtils = require("./MathUtils.js"); var JSUtils = require("./JSUtils.js"); @@ -33,175 +33,175 @@ var IString = require("./IString.js"); /** * @class - * Create a time zone instance. - * + * Create a time zone instance. + * * This class reports and transforms * information about particular time zones.

    - * + * * The options parameter may contain any of the following properties: - * + * *

      - *
    • id - The id of the requested time zone such as "Europe/London" or + *
    • id - The id of the requested time zone such as "Europe/London" or * "America/Los_Angeles". These are taken from the IANA time zone database. (See * http://www.iana.org/time-zones for more information.)

      - * - * There is one special + * + * There is one special * time zone that is not taken from the IANA database called simply "local". In * this case, this class will attempt to discover the current time zone and - * daylight savings time settings by calling standard Javascript classes to - * determine the offsets from UTC. - * + * daylight savings time settings by calling standard Javascript classes to + * determine the offsets from UTC. + * *

    • locale - The locale for this time zone. - * + * *
    • offset - Choose the time zone based on the offset from UTC given in * number of minutes (negative is west, positive is east). - * - *
    • onLoad - a callback function to call when the data is fully + * + *
    • onLoad - a callback function to call when the data is fully * loaded. When the onLoad option is given, this class will attempt to * load any missing locale data using the ilib loader callback. - * When the data is loaded, the onLoad function is called with the current - * instance as a parameter. - * - *
    • sync - tell whether to load any missing locale data synchronously or + * When the data is loaded, the onLoad function is called with the current + * instance as a parameter. + * + *
    • sync - tell whether to load any missing locale data synchronously or * asynchronously. If this option is given as "false", then the "onLoad" * callback must be given, as the instance returned from this constructor will * not be usable for a while. - * - *
    • loadParams - an object containing parameters to pass to the + * + *
    • loadParams - an object containing parameters to pass to the * loader callback function when locale data is missing. The parameters are not - * interpretted or modified in any way. They are simply passed along. The object + * interpretted or modified in any way. They are simply passed along. The object * may contain any property/value pairs as long as the calling code is in * agreement with the loader callback function as to what those parameters mean. *
    - * + * * There is currently no way in the ECMAscript * standard to tell which exact time zone is currently in use. Choosing the - * id "locale" or specifying an explicit offset will not give a specific time zone, - * as it is impossible to tell with certainty which zone the offsets + * id "locale" or specifying an explicit offset will not give a specific time zone, + * as it is impossible to tell with certainty which zone the offsets * match.

    - * + * * When the id "local" is given or the offset option is specified, this class will * have the following behaviours: *

      *
    • The display name will always be given as the RFC822 style, no matter what * style is requested *
    • The id will also be returned as the RFC822 style display name - *
    • When the offset is explicitly given, this class will assume the time zone - * does not support daylight savings time, and the offsets will be calculated + *
    • When the offset is explicitly given, this class will assume the time zone + * does not support daylight savings time, and the offsets will be calculated * the same way year round. - *
    • When the offset is explicitly given, the inDaylightSavings() method will + *
    • When the offset is explicitly given, the inDaylightSavings() method will * always return false. - *
    • When the id "local" is given, this class will attempt to determine the + *
    • When the id "local" is given, this class will attempt to determine the * daylight savings time settings by examining the offset from UTC on Jan 1 * and June 1 of the current year. If they are different, this class assumes * that the local time zone uses DST. When the offset for a particular date is - * requested, it will use the built-in Javascript support to determine the + * requested, it will use the built-in Javascript support to determine the * offset for that date. - *
    - * - * If a more specific time zone is - * needed with display names and known start/stop times for DST, use the "id" + * + * + * If a more specific time zone is + * needed with display names and known start/stop times for DST, use the "id" * property instead to specify the time zone exactly. You can perhaps ask the * user which time zone they prefer so that your app does not need to guess.

    - * - * If the id and the offset are both not given, the default time zone for the + * + * If the id and the offset are both not given, the default time zone for the * locale is retrieved from * the locale info. If the locale is not specified, the default locale for the * library is used.

    - * + * * Because this class was designed for use in web sites, and the vast majority * of dates and times being formatted are recent date/times, this class is simplified - * by not implementing historical time zones. That is, when governments change the - * time zone rules for a particular zone, only the latest such rule is implemented - * in this class. That means that determining the offset for a date that is prior + * by not implementing historical time zones. That is, when governments change the + * time zone rules for a particular zone, only the latest such rule is implemented + * in this class. That means that determining the offset for a date that is prior * to the last change may give the wrong result. Historical time zone calculations * may be implemented in a later version of iLib if there is enough demand for it, * but it would entail a much larger set of time zone data that would have to be - * loaded. - * - * + * loaded. + * + * * @constructor * @param {Object} options Options guiding the construction of this time zone instance */ var TimeZone = function(options) { - this.sync = true; - this.locale = new Locale(); - this.isLocal = false; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (options.id) { - var id = options.id.toString(); - if (id === 'local') { - this.isLocal = true; - - // use standard Javascript Date to figure out the time zone offsets - var now = new Date(), - jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based - jun1 = new Date(now.getFullYear(), 5, 1); - - // Javascript's method returns the offset backwards, so we have to - // take the negative to get the correct offset - this.offsetJan1 = -jan1.getTimezoneOffset(); - this.offsetJun1 = -jun1.getTimezoneOffset(); - // the offset of the standard time for the time zone is always the one that is closest - // to negative infinity of the two, no matter whether you are in the northern or southern - // hemisphere, east or west - this.offset = Math.min(this.offsetJan1, this.offsetJun1); - } - this.id = id; - } else if (options.offset) { - this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; - this.id = this.getDisplayName(undefined, undefined); - } - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - this.loadParams = options.loadParams; - this.onLoad = options.onLoad; - } - - //console.log("timezone: locale is " + this.locale); - - if (!this.id) { - new LocaleInfo(this.locale, { - sync: this.sync, - loadParams: this.loadParams, - onLoad: ilib.bind(this, function (li) { - this.id = li.getTimeZone() || "Etc/UTC"; - this._loadtzdata(); - }) - }); - } else { - this._loadtzdata(); - } - - //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); - //console.log("id is: " + JSON.stringify(this.id)); + this.sync = true; + this.locale = new Locale(); + this.isLocal = false; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (options.id) { + var id = options.id.toString(); + if (id === 'local') { + this.isLocal = true; + + // use standard Javascript Date to figure out the time zone offsets + var now = new Date(), + jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based + jun1 = new Date(now.getFullYear(), 5, 1); + + // Javascript's method returns the offset backwards, so we have to + // take the negative to get the correct offset + this.offsetJan1 = -jan1.getTimezoneOffset(); + this.offsetJun1 = -jun1.getTimezoneOffset(); + // the offset of the standard time for the time zone is always the one that is closest + // to negative infinity of the two, no matter whether you are in the northern or southern + // hemisphere, east or west + this.offset = Math.min(this.offsetJan1, this.offsetJun1); + } + this.id = id; + } else if (options.offset) { + this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; + this.id = this.getDisplayName(undefined, undefined); + } + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + this.loadParams = options.loadParams; + this.onLoad = options.onLoad; + } + + //console.log("timezone: locale is " + this.locale); + + if (!this.id) { + new LocaleInfo(this.locale, { + sync: this.sync, + loadParams: this.loadParams, + onLoad: ilib.bind(this, function (li) { + this.id = li.getTimeZone() || "Etc/UTC"; + this._loadtzdata(); + }) + }); + } else { + this._loadtzdata(); + } + + //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); + //console.log("id is: " + JSON.stringify(this.id)); }; /* * Explanation of the compressed time zone info properties. * { * "o": "8:0", // offset from UTC - * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the - * // letter in the e.c or s.c properties below + * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the + * // letter in the e.c or s.c properties below * "e": { // info about the end of DST - * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and + * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and * // "t" properties, but not both sets. * "m": 3, // month that it ends - * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". + * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= * "t": "2:0", // time of day that the DST turns off, hours:minutes - * "c": "S" // character to replace into the abbreviation for standard time + * "c": "S" // character to replace into the abbreviation for standard time * }, * "s": { // info about the start of DST - * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and + * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and * // "t" properties, but not both sets. * "m": 10, // month that it starts * "r": "l0", // rule for the day it starts "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". @@ -216,98 +216,98 @@ var TimeZone = function(options) { * } */ TimeZone.prototype._loadtzdata = function () { - var zoneName = this.id.replace(/-/g, "m").replace(/\+/g, "p"); - // console.log("id is: " + JSON.stringify(this.id)); - // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[zoneName])); - if (!ilib.data.zoneinfo[zoneName] && typeof(this.offset) === 'undefined') { - Utils.loadData({ - object: "TimeZone", - nonlocale: true, // locale independent - name: "zoneinfo/" + this.id + ".json", - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (tzdata) { - if (tzdata && !JSUtils.isEmpty(tzdata)) { - ilib.data.zoneinfo[zoneName] = tzdata; - } - this._initZone(zoneName); - }) - }); - } else { - this._initZone(zoneName); - } + var zoneName = this.id.replace(/-/g, "m").replace(/\+/g, "p"); + // console.log("id is: " + JSON.stringify(this.id)); + // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[zoneName])); + if (!ilib.data.zoneinfo[zoneName] && typeof(this.offset) === 'undefined') { + Utils.loadData({ + object: "TimeZone", + nonlocale: true, // locale independent + name: "zoneinfo/" + this.id + ".json", + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (tzdata) { + if (tzdata && !JSUtils.isEmpty(tzdata)) { + ilib.data.zoneinfo[zoneName] = tzdata; + } + this._initZone(zoneName); + }) + }); + } else { + this._initZone(zoneName); + } }; TimeZone.prototype._initZone = function(zoneName) { - /** - * @private - * @type {{o:string,f:string,e:Object.<{m:number,r:string,t:string,z:string}>,s:Object.<{m:number,r:string,t:string,z:string,v:string,c:string}>,c:string,n:string}} - */ - this.zone = ilib.data.zoneinfo[zoneName]; - if (!this.zone && typeof(this.offset) === 'undefined') { - this.id = "Etc/UTC"; - this.zone = ilib.data.zoneinfo[this.id]; - } - - this._calcDSTSavings(); - - if (typeof(this.offset) === 'undefined' && this.zone.o) { - var offsetParts = this._offsetStringToObj(this.zone.o); - /** - * @private - * @type {number} raw offset from UTC without DST, in minutes - */ - this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); - } - - if (this.onLoad && typeof(this.onLoad) === 'function') { - this.onLoad(this); - } + /** + * @private + * @type {{o:string,f:string,e:Object.<{m:number,r:string,t:string,z:string}>,s:Object.<{m:number,r:string,t:string,z:string,v:string,c:string}>,c:string,n:string}} + */ + this.zone = ilib.data.zoneinfo[zoneName]; + if (!this.zone && typeof(this.offset) === 'undefined') { + this.id = "Etc/UTC"; + this.zone = ilib.data.zoneinfo[this.id]; + } + + this._calcDSTSavings(); + + if (typeof(this.offset) === 'undefined' && this.zone.o) { + var offsetParts = this._offsetStringToObj(this.zone.o); + /** + * @private + * @type {number} raw offset from UTC without DST, in minutes + */ + this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); + } + + if (this.onLoad && typeof(this.onLoad) === 'function') { + this.onLoad(this); + } }; /** @private */ TimeZone._marshallIds = function (country, sync, callback) { - var tz, ids = []; - - if (!country) { - // local is a special zone meaning "the local time zone according to the JS engine we are running upon" - ids.push("local"); - for (tz in ilib.data.timezones) { - if (ilib.data.timezones[tz]) { - ids.push(ilib.data.timezones[tz]); - } - } - if (typeof(callback) === 'function') { - callback(ids); - } - } else { - if (!ilib.data.zoneinfo.zonetab) { - Utils.loadData({ - object: "TimeZone", - nonlocale: true, // locale independent - name: "zoneinfo/zonetab.json", - sync: sync, - callback: ilib.bind(this, function (tzdata) { - if (tzdata) { - ilib.data.zoneinfo.zonetab = tzdata; - } - - ids = ilib.data.zoneinfo.zonetab[country]; - - if (typeof(callback) === 'function') { - callback(ids); - } - }) - }); - } else { - ids = ilib.data.zoneinfo.zonetab[country]; - if (typeof(callback) === 'function') { - callback(ids); - } - } - } - - return ids; + var tz, ids = []; + + if (!country) { + // local is a special zone meaning "the local time zone according to the JS engine we are running upon" + ids.push("local"); + for (tz in ilib.data.timezones) { + if (ilib.data.timezones[tz]) { + ids.push(ilib.data.timezones[tz]); + } + } + if (typeof(callback) === 'function') { + callback(ids); + } + } else { + if (!ilib.data.zoneinfo.zonetab) { + Utils.loadData({ + object: "TimeZone", + nonlocale: true, // locale independent + name: "zoneinfo/zonetab.json", + sync: sync, + callback: ilib.bind(this, function (tzdata) { + if (tzdata) { + ilib.data.zoneinfo.zonetab = tzdata; + } + + ids = ilib.data.zoneinfo.zonetab[country]; + + if (typeof(callback) === 'function') { + callback(ids); + } + }) + }); + } else { + ids = ilib.data.zoneinfo.zonetab[country]; + if (typeof(callback) === 'function') { + callback(ids); + } + } + } + + return ids; }; /** @@ -315,47 +315,47 @@ TimeZone._marshallIds = function (country, sync, callback) { * The country parameter is optional. If it is not given, all time zones will * be returned. If it specifies a country code, then only time zones for that * country will be returned. - * + * * @param {string|undefined} country country code for which time zones are being sought * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) * @param {function(Array.)} onLoad callback function to call when the data is finished loading * @return {Array.} an array of zone id strings */ TimeZone.getAvailableIds = function (country, sync, onLoad) { - var tz, ids = []; - - if (typeof(sync) !== 'boolean') { - sync = true; - } - - if (ilib.data.timezones.length === 0) { - if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { - ilib._load.listAvailableFiles(sync, function(hash) { - for (var dir in hash) { - var files = hash[dir]; - if (ilib.isArray(files)) { - files.forEach(function (filename) { - if (filename && filename.match(/^zoneinfo/)) { - ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); - } - }); - } - } - ids = TimeZone._marshallIds(country, sync, onLoad); - }); - } else { - for (tz in ilib.data.zoneinfo) { - if (ilib.data.zoneinfo[tz]) { - ilib.data.timezones.push(tz); - } - } - ids = TimeZone._marshallIds(country, sync, onLoad); - } - } else { - ids = TimeZone._marshallIds(country, sync, onLoad); - } - - return ids; + var tz, ids = []; + + if (typeof(sync) !== 'boolean') { + sync = true; + } + + if (ilib.data.timezones.length === 0) { + if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { + ilib._load.listAvailableFiles(sync, function(hash) { + for (var dir in hash) { + var files = hash[dir]; + if (ilib.isArray(files)) { + files.forEach(function (filename) { + if (filename && filename.match(/^zoneinfo/)) { + ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); + } + }); + } + } + ids = TimeZone._marshallIds(country, sync, onLoad); + }); + } else { + for (tz in ilib.data.zoneinfo) { + if (ilib.data.zoneinfo[tz]) { + ilib.data.timezones.push(tz); + } + } + ids = TimeZone._marshallIds(country, sync, onLoad); + } + } else { + ids = TimeZone._marshallIds(country, sync, onLoad); + } + + return ids; }; /** @@ -363,172 +363,172 @@ TimeZone.getAvailableIds = function (country, sync, onLoad) { * @return {string} a unique id for this time zone */ TimeZone.prototype.getId = function () { - return this.id.toString(); + return this.id.toString(); }; /** * Return the abbreviation that is used for the current time zone on the given date. * The date may be in DST or during standard time, and many zone names have different * abbreviations depending on whether or not the date is falls within DST.

    - * + * * There are two styles that are supported: - * + * *

      - *
    1. standard - returns the 3 to 5 letter abbreviation of the time zone name such + *
    2. standard - returns the 3 to 5 letter abbreviation of the time zone name such * as "CET" for "Central European Time" or "PDT" for "Pacific Daylight Time" *
    3. rfc822 - returns an RFC 822 style time zone specifier, which specifies more * explicitly what the offset is from UTC *
    4. long - returns the long name of the zone in English *
    - * + * * @param {IDate=} date a date to determine if it is in daylight time or standard time * @param {string=} style one of "standard" or "rfc822". Default if not specified is "standard" - * @return {string} the name of the time zone, abbreviated according to the style + * @return {string} the name of the time zone, abbreviated according to the style */ TimeZone.prototype.getDisplayName = function (date, style) { var temp; - style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); - switch (style) { - default: - case 'standard': - if (this.zone.f && this.zone.f !== "zzz") { - if (this.zone.f.indexOf("{c}") !== -1) { - var letter = ""; - letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; - temp = new IString(this.zone.f); - return temp.format({c: letter || ""}); - } - return this.zone.f; - } - temp = "GMT" + this.zone.o; - if (this.inDaylightTime(date)) { - temp += "+" + this.zone.s.v; - } - return temp; - - case 'rfc822': - var offset = this.getOffset(date), // includes the DST if applicable - ret = "UTC", - hour = offset.h || 0, - minute = offset.m || 0; - - if (hour !== 0) { - ret += (hour > 0) ? "+" : "-"; - if (Math.abs(hour) < 10) { - ret += "0"; - } - ret += (hour < 0) ? -hour : hour; - if (minute < 10) { - ret += "0"; - } - ret += minute; - } - return ret; - - case 'long': - if (this.zone.n) { - if (this.zone.n.indexOf("{c}") !== -1) { - var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; - temp = new IString(this.zone.n); - return temp.format({c: str || ""}); - } - return this.zone.n; - } - temp = "GMT" + this.zone.o; - if (this.inDaylightTime(date)) { - temp += "+" + this.zone.s.v; - } - return temp; - } + style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); + switch (style) { + default: + case 'standard': + if (this.zone.f && this.zone.f !== "zzz") { + if (this.zone.f.indexOf("{c}") !== -1) { + var letter = ""; + letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; + temp = new IString(this.zone.f); + return temp.format({c: letter || ""}); + } + return this.zone.f; + } + temp = "GMT" + this.zone.o; + if (this.inDaylightTime(date)) { + temp += "+" + this.zone.s.v; + } + return temp; + + case 'rfc822': + var offset = this.getOffset(date), // includes the DST if applicable + ret = "UTC", + hour = offset.h || 0, + minute = offset.m || 0; + + if (hour !== 0) { + ret += (hour > 0) ? "+" : "-"; + if (Math.abs(hour) < 10) { + ret += "0"; + } + ret += (hour < 0) ? -hour : hour; + if (minute < 10) { + ret += "0"; + } + ret += minute; + } + return ret; + + case 'long': + if (this.zone.n) { + if (this.zone.n.indexOf("{c}") !== -1) { + var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; + temp = new IString(this.zone.n); + return temp.format({c: str || ""}); + } + return this.zone.n; + } + temp = "GMT" + this.zone.o; + if (this.inDaylightTime(date)) { + temp += "+" + this.zone.s.v; + } + return temp; + } }; /** * Convert the offset string to an object with an h, m, and possibly s property * to indicate the hours, minutes, and seconds. - * + * * @private * @param {string} str the offset string to convert to an object - * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at + * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at * the given date/time, in hours, minutes, and seconds */ TimeZone.prototype._offsetStringToObj = function (str) { - var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], - ret = {h:0}, - temp; - - if (offsetParts.length > 0) { - ret.h = parseInt(offsetParts[0], 10); - if (offsetParts.length > 1) { - temp = parseInt(offsetParts[1], 10); - if (temp) { - ret.m = temp; - } - if (offsetParts.length > 2) { - temp = parseInt(offsetParts[2], 10); - if (temp) { - ret.s = temp; - } - } - } - } - - return ret; + var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], + ret = {h:0}, + temp; + + if (offsetParts.length > 0) { + ret.h = parseInt(offsetParts[0], 10); + if (offsetParts.length > 1) { + temp = parseInt(offsetParts[1], 10); + if (temp) { + ret.m = temp; + } + if (offsetParts.length > 2) { + temp = parseInt(offsetParts[2], 10); + if (temp) { + ret.s = temp; + } + } + } + } + + return ret; }; /** - * Returns the offset of this time zone from UTC at the given date/time. If daylight saving - * time is in effect at the given date/time, this method will return the offset value + * Returns the offset of this time zone from UTC at the given date/time. If daylight saving + * time is in effect at the given date/time, this method will return the offset value * adjusted by the amount of daylight saving. * @param {IDate=} date the date for which the offset is needed - * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at - * the given date/time, in hours, minutes, and seconds + * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at + * the given date/time, in hours, minutes, and seconds */ TimeZone.prototype.getOffset = function (date) { - if (!date) { - return this.getRawOffset(); - } - var offset = this.getOffsetMillis(date)/60000; - - var hours = MathUtils.down(offset/60), - minutes = Math.abs(offset) - Math.abs(hours)*60; - - var ret = { - h: hours - }; - if (minutes != 0) { - ret.m = minutes; - } - return ret; + if (!date) { + return this.getRawOffset(); + } + var offset = this.getOffsetMillis(date)/60000; + + var hours = MathUtils.down(offset/60), + minutes = Math.abs(offset) - Math.abs(hours)*60; + + var ret = { + h: hours + }; + if (minutes != 0) { + ret.m = minutes; + } + return ret; }; /** - * Returns the offset of this time zone from UTC at the given date/time expressed in - * milliseconds. If daylight saving - * time is in effect at the given date/time, this method will return the offset value + * Returns the offset of this time zone from UTC at the given date/time expressed in + * milliseconds. If daylight saving + * time is in effect at the given date/time, this method will return the offset value * adjusted by the amount of daylight saving. Negative numbers indicate offsets west * of UTC and conversely, positive numbers indicate offset east of UTC. - * + * * @param {IDate=} date the date for which the offset is needed, or null for the * present date * @return {number} the number of milliseconds of offset from UTC that the given date is */ TimeZone.prototype.getOffsetMillis = function (date) { - var ret; - - // check if the dst property is defined -- the intrinsic JS Date object doesn't work so - // well if we are in the overlap time at the end of DST - if (this.isLocal && typeof(date.dst) === 'undefined') { - var d = (!date) ? new Date() : new Date(date.getTimeExtended()); - return -d.getTimezoneOffset() * 60000; - } - - ret = this.offset; - - if (date && this.inDaylightTime(date)) { - ret += this.dstSavings; - } - - return ret * 60000; + var ret; + + // check if the dst property is defined -- the intrinsic JS Date object doesn't work so + // well if we are in the overlap time at the end of DST + if (this.isLocal && typeof(date.dst) === 'undefined') { + var d = (!date) ? new Date() : new Date(date.getTimeExtended()); + return -d.getTimezoneOffset() * 60000; + } + + ret = this.offset; + + if (date && this.inDaylightTime(date)) { + ret += this.dstSavings; + } + + return ret * 60000; }; /** @@ -539,101 +539,101 @@ TimeZone.prototype.getOffsetMillis = function (date) { * @returns {number} the number of milliseconds of offset from UTC that the given date is */ TimeZone.prototype._getOffsetMillisWallTime = function (date) { - var ret; - - ret = this.offset; - - if (date && this.inDaylightTime(date, true)) { - ret += this.dstSavings; - } - - return ret * 60000; + var ret; + + ret = this.offset; + + if (date && this.inDaylightTime(date, true)) { + ret += this.dstSavings; + } + + return ret * 60000; }; /** - * Returns the offset of this time zone from UTC at the given date/time. If daylight saving - * time is in effect at the given date/time, this method will return the offset value + * Returns the offset of this time zone from UTC at the given date/time. If daylight saving + * time is in effect at the given date/time, this method will return the offset value * adjusted by the amount of daylight saving. * @param {IDate=} date the date for which the offset is needed - * @return {string} the offset for the zone at the given date/time as a string in the - * format "h:m:s" + * @return {string} the offset for the zone at the given date/time as a string in the + * format "h:m:s" */ TimeZone.prototype.getOffsetStr = function (date) { - var offset = this.getOffset(date), - ret; - - ret = offset.h; - if (typeof(offset.m) !== 'undefined') { - ret += ":" + offset.m; - if (typeof(offset.s) !== 'undefined') { - ret += ":" + offset.s; - } - } else { - ret += ":0"; - } - - return ret; + var offset = this.getOffset(date), + ret; + + ret = offset.h; + if (typeof(offset.m) !== 'undefined') { + ret += ":" + offset.m; + if (typeof(offset.s) !== 'undefined') { + ret += ":" + offset.s; + } + } else { + ret += ":0"; + } + + return ret; }; /** * Gets the offset from UTC for this time zone. - * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from - * UTC for this time zone, in hours, minutes, and seconds + * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from + * UTC for this time zone, in hours, minutes, and seconds */ TimeZone.prototype.getRawOffset = function () { - var hours = MathUtils.down(this.offset/60), - minutes = Math.abs(this.offset) - Math.abs(hours)*60; - - var ret = { - h: hours - }; - if (minutes != 0) { - ret.m = minutes; - } - return ret; + var hours = MathUtils.down(this.offset/60), + minutes = Math.abs(this.offset) - Math.abs(hours)*60; + + var ret = { + h: hours + }; + if (minutes != 0) { + ret.m = minutes; + } + return ret; }; /** * Gets the offset from UTC for this time zone expressed in milliseconds. Negative numbers * indicate zones west of UTC, and positive numbers indicate zones east of UTC. - * - * @return {number} an number giving the offset from - * UTC for this time zone in milliseconds + * + * @return {number} an number giving the offset from + * UTC for this time zone in milliseconds */ TimeZone.prototype.getRawOffsetMillis = function () { - return this.offset * 60000; + return this.offset * 60000; }; /** * Gets the offset from UTC for this time zone without DST savings. - * @return {string} the offset from UTC for this time zone, in the format "h:m:s" + * @return {string} the offset from UTC for this time zone, in the format "h:m:s" */ TimeZone.prototype.getRawOffsetStr = function () { - var off = this.getRawOffset(); - return off.h + ":" + (off.m || "0"); + var off = this.getRawOffset(); + return off.h + ":" + (off.m || "0"); }; /** * Return the amount of time in hours:minutes that the clock is advanced during * daylight savings time. - * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the - * clock advances for DST in hours, minutes, and seconds + * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the + * clock advances for DST in hours, minutes, and seconds */ TimeZone.prototype.getDSTSavings = function () { - if (this.isLocal) { - // take the absolute because the difference in the offsets may be positive or - // negative, depending on the hemisphere - var savings = Math.abs(this.offsetJan1 - this.offsetJun1); - var hours = MathUtils.down(savings/60), - minutes = savings - hours*60; - return { - h: hours, - m: minutes - }; - } else if (this.zone && this.zone.s) { - return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings - } - return {h:0}; + if (this.isLocal) { + // take the absolute because the difference in the offsets may be positive or + // negative, depending on the hemisphere + var savings = Math.abs(this.offsetJan1 - this.offsetJun1); + var hours = MathUtils.down(savings/60), + minutes = savings - hours*60; + return { + h: hours, + m: minutes + }; + } else if (this.zone && this.zone.s) { + return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings + } + return {h:0}; }; /** @@ -643,13 +643,13 @@ TimeZone.prototype.getDSTSavings = function () { * format "h:m:s" */ TimeZone.prototype.getDSTSavingsStr = function () { - if (this.isLocal) { - var savings = this.getDSTSavings(); - return savings.h + ":" + savings.m; - } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { - return this.zone.s.v; // this.zone.start.savings - } - return "0:0"; + if (this.isLocal) { + var savings = this.getDSTSavings(); + return savings.h + ":" + savings.m; + } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { + return this.zone.s.v; // this.zone.start.savings + } + return "0:0"; }; /** @@ -660,113 +660,113 @@ TimeZone.prototype.getDSTSavingsStr = function () { * @return {number} the rd of the start of DST for the year */ TimeZone.prototype._calcRuleStart = function (rule, year) { - var type = "=", - weekday = 0, - day, - refDay, - cal, - hour = 0, - minute = 0, - second = 0, - time, - i; - - if (typeof(rule.j) !== 'undefined') { - refDay = new GregRataDie({ - julianday: rule.j - }); - } else { - if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { - cal = CalendarFactory({type: "gregorian"}); // can be synchronous - type = rule.r.charAt(0); - weekday = parseInt(rule.r.substring(1), 10); - day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; - //console.log("_calcRuleStart: Calculating the " + - // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + - // " of month " + rule.m); - } else { - i = rule.r.indexOf('<'); - if (i == -1) { - i = rule.r.indexOf('>'); - } - - if (i != -1) { - type = rule.r.charAt(i); - weekday = parseInt(rule.r.substring(0, i), 10); - day = parseInt(rule.r.substring(i+1), 10); - //console.log("_calcRuleStart: Calculating the " + weekday + - // type + day + " of month " + rule.m); - } else { - day = parseInt(rule.r, 10); - //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); - } - } - - if (rule.t) { - time = rule.t.split(":"); - hour = parseInt(time[0], 10); - if (time.length > 1) { - minute = parseInt(time[1], 10); - if (time.length > 2) { - second = parseInt(time[2], 10); - } - } - } - //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); - refDay = new GregRataDie({ - year: year, - month: rule.m, - day: day, - hour: hour, - minute: minute, - second: second - }); - } - //console.log("refDay is " + JSON.stringify(refDay)); - var d = refDay.getRataDie(); - - switch (type) { - case 'l': - case '<': - //console.log("returning " + refDay.onOrBefore(rd, weekday)); - d = refDay.onOrBefore(weekday); - break; - case 'f': - case '>': - //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); - d = refDay.onOrAfter(weekday); - break; - } - return d; + var type = "=", + weekday = 0, + day, + refDay, + cal, + hour = 0, + minute = 0, + second = 0, + time, + i; + + if (typeof(rule.j) !== 'undefined') { + refDay = new GregRataDie({ + julianday: rule.j + }); + } else { + if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { + cal = CalendarFactory({type: "gregorian"}); // can be synchronous + type = rule.r.charAt(0); + weekday = parseInt(rule.r.substring(1), 10); + day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; + //console.log("_calcRuleStart: Calculating the " + + // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + + // " of month " + rule.m); + } else { + i = rule.r.indexOf('<'); + if (i == -1) { + i = rule.r.indexOf('>'); + } + + if (i != -1) { + type = rule.r.charAt(i); + weekday = parseInt(rule.r.substring(0, i), 10); + day = parseInt(rule.r.substring(i+1), 10); + //console.log("_calcRuleStart: Calculating the " + weekday + + // type + day + " of month " + rule.m); + } else { + day = parseInt(rule.r, 10); + //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); + } + } + + if (rule.t) { + time = rule.t.split(":"); + hour = parseInt(time[0], 10); + if (time.length > 1) { + minute = parseInt(time[1], 10); + if (time.length > 2) { + second = parseInt(time[2], 10); + } + } + } + //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); + refDay = new GregRataDie({ + year: year, + month: rule.m, + day: day, + hour: hour, + minute: minute, + second: second + }); + } + //console.log("refDay is " + JSON.stringify(refDay)); + var d = refDay.getRataDie(); + + switch (type) { + case 'l': + case '<': + //console.log("returning " + refDay.onOrBefore(rd, weekday)); + d = refDay.onOrBefore(weekday); + break; + case 'f': + case '>': + //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); + d = refDay.onOrAfter(weekday); + break; + } + return d; }; /** * @private */ TimeZone.prototype._calcDSTSavings = function () { - var saveParts = this.getDSTSavings(); - - /** - * @private - * @type {number} savings in minutes when DST is in effect - */ - this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); + var saveParts = this.getDSTSavings(); + + /** + * @private + * @type {number} savings in minutes when DST is in effect + */ + this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); }; /** * @private */ TimeZone.prototype._getDSTStartRule = function (year) { - // TODO: update this when historic/future zones are supported - return this.zone.s; + // TODO: update this when historic/future zones are supported + return this.zone.s; }; /** * @private */ TimeZone.prototype._getDSTEndRule = function (year) { - // TODO: update this when historic/future zones are supported - return this.zone.e; + // TODO: update this when historic/future zones are supported + return this.zone.e; }; /** @@ -776,7 +776,7 @@ TimeZone.prototype._getDSTEndRule = function (year) { * runs from the end of the year through New Years into the first few months of the * next year. This method will correctly calculate the start and end of DST for any * location. - * + * * @param {IDate=} date a date for which the info about daylight time is being sought, * or undefined to tell whether we are currently in daylight savings time * @param {boolean=} wallTime if true, then the given date is in wall time. If false or @@ -785,81 +785,81 @@ TimeZone.prototype._getDSTEndRule = function (year) { * otherwise. */ TimeZone.prototype.inDaylightTime = function (date, wallTime) { - var rd, startRd, endRd, year; - - if (this.isLocal) { - // check if the dst property is defined -- the intrinsic JS Date object doesn't work so - // well if we are in the overlap time at the end of DST, so we have to work around that - // problem by adding in the savings ourselves - var offset = this.offset * 60000; - if (typeof(date.dst) !== 'undefined' && !date.dst) { - offset += this.dstSavings * 60000; - } - - var d = new Date(date ? date.getTimeExtended() - offset: undefined); - // the DST offset is always the one that is closest to positive infinity, no matter - // if you are in the northern or southern hemisphere, east or west - var dst = Math.max(this.offsetJan1, this.offsetJun1); - return (-d.getTimezoneOffset() === dst); - } - - if (!date || !date.cal || date.cal.type !== "gregorian") { - // convert to Gregorian so that we can tell if it is in DST or not - var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; - rd = new GregRataDie({unixtime: time}).getRataDie(); - year = new Date(time).getUTCFullYear(); - } else { - rd = date.rd.getRataDie(); - year = date.year; - } - // rd should be a Gregorian RD number now, in UTC - - // if we aren't using daylight time in this zone for the given year, then we are - // not in daylight time - if (!this.useDaylightTime(year)) { - return false; - } - - // these calculate the start/end in local wall time - var startrule = this._getDSTStartRule(year); - var endrule = this._getDSTEndRule(year); - startRd = this._calcRuleStart(startrule, year); - endRd = this._calcRuleStart(endrule, year); - - if (wallTime) { - // rd is in wall time, so we have to make sure to skip the missing time - // at the start of DST when standard time ends and daylight time begins - startRd += this.dstSavings/1440; - } else { - // rd is in UTC, so we have to convert the start/end to UTC time so - // that they can be compared directly to the UTC rd number of the date - - // when DST starts, time is standard time already, so we only have - // to subtract the offset to get to UTC and not worry about the DST savings - startRd -= this.offset/1440; - - // when DST ends, time is in daylight time already, so we have to - // subtract the DST savings to get back to standard time, then the - // offset to get to UTC - endRd -= (this.offset + this.dstSavings)/1440; - } - - // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), - // then the end some time in the fall (Sept-Nov). In the southern - // hemisphere, it is the other way around because the seasons are reversed. Standard - // time is still in the winter, but the winter months are May-Aug, and daylight - // savings time usually starts Aug-Oct of one year and runs through Mar-May of the - // next year. - if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { - // take care of the magic overlap time at the end of DST - return date.dst; - } - if (startRd < endRd) { - // northern hemisphere - return (rd >= startRd && rd < endRd) ? true : false; - } - // southern hemisphere - return (rd >= startRd || rd < endRd) ? true : false; + var rd, startRd, endRd, year; + + if (this.isLocal) { + // check if the dst property is defined -- the intrinsic JS Date object doesn't work so + // well if we are in the overlap time at the end of DST, so we have to work around that + // problem by adding in the savings ourselves + var offset = this.offset * 60000; + if (typeof(date.dst) !== 'undefined' && !date.dst) { + offset += this.dstSavings * 60000; + } + + var d = new Date(date ? date.getTimeExtended() - offset: undefined); + // the DST offset is always the one that is closest to positive infinity, no matter + // if you are in the northern or southern hemisphere, east or west + var dst = Math.max(this.offsetJan1, this.offsetJun1); + return (-d.getTimezoneOffset() === dst); + } + + if (!date || !date.cal || date.cal.type !== "gregorian") { + // convert to Gregorian so that we can tell if it is in DST or not + var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; + rd = new GregRataDie({unixtime: time}).getRataDie(); + year = new Date(time).getUTCFullYear(); + } else { + rd = date.rd.getRataDie(); + year = date.year; + } + // rd should be a Gregorian RD number now, in UTC + + // if we aren't using daylight time in this zone for the given year, then we are + // not in daylight time + if (!this.useDaylightTime(year)) { + return false; + } + + // these calculate the start/end in local wall time + var startrule = this._getDSTStartRule(year); + var endrule = this._getDSTEndRule(year); + startRd = this._calcRuleStart(startrule, year); + endRd = this._calcRuleStart(endrule, year); + + if (wallTime) { + // rd is in wall time, so we have to make sure to skip the missing time + // at the start of DST when standard time ends and daylight time begins + startRd += this.dstSavings/1440; + } else { + // rd is in UTC, so we have to convert the start/end to UTC time so + // that they can be compared directly to the UTC rd number of the date + + // when DST starts, time is standard time already, so we only have + // to subtract the offset to get to UTC and not worry about the DST savings + startRd -= this.offset/1440; + + // when DST ends, time is in daylight time already, so we have to + // subtract the DST savings to get back to standard time, then the + // offset to get to UTC + endRd -= (this.offset + this.dstSavings)/1440; + } + + // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), + // then the end some time in the fall (Sept-Nov). In the southern + // hemisphere, it is the other way around because the seasons are reversed. Standard + // time is still in the winter, but the winter months are May-Aug, and daylight + // savings time usually starts Aug-Oct of one year and runs through Mar-May of the + // next year. + if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { + // take care of the magic overlap time at the end of DST + return date.dst; + } + if (startRd < endRd) { + // northern hemisphere + return (rd >= startRd && rd < endRd) ? true : false; + } + // southern hemisphere + return (rd >= startRd || rd < endRd) ? true : false; }; /** @@ -870,13 +870,13 @@ TimeZone.prototype.inDaylightTime = function (date, wallTime) { * @return {boolean} true if the time zone uses daylight savings time */ TimeZone.prototype.useDaylightTime = function (year) { - - // this zone uses daylight savings time iff there is a rule defining when to start - // and when to stop the DST - return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || - (typeof(this.zone) !== 'undefined' && - typeof(this.zone.s) !== 'undefined' && - typeof(this.zone.e) !== 'undefined'); + + // this zone uses daylight savings time iff there is a rule defining when to start + // and when to stop the DST + return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || + (typeof(this.zone) !== 'undefined' && + typeof(this.zone.s) !== 'undefined' && + typeof(this.zone.e) !== 'undefined'); }; /** @@ -884,7 +884,7 @@ TimeZone.prototype.useDaylightTime = function (year) { * @return {string} the ISO 3166 code of the country for this zone */ TimeZone.prototype.getCountry = function () { - return this.zone.c; + return this.zone.c; }; module.exports = TimeZone; diff --git a/js/lib/UTF16BE.js b/js/lib/UTF16BE.js index a5051ecbaa..08d603a69c 100644 --- a/js/lib/UTF16BE.js +++ b/js/lib/UTF16BE.js @@ -1,7 +1,7 @@ /* * UTF16BE.js - Implement Unicode Transformation Format 16-bit, * Big Endian mappings - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -64,35 +64,35 @@ UTF16BE.prototype._init = function(options) { }; UTF16BE.prototype.mapToUnicode = function (bytes) { - // nodejs can't convert big-endian in native code, - // so we would have to flip each Uint16 ourselves. - // At that point, it's just quicker to convert - // in JS code anyways - var ret = ""; - for (var i = 0; i < bytes.length; i += 2) { - ret += String.fromCharCode(bytes[i] << 8 | bytes[i+1]); - } - - return ret; + // nodejs can't convert big-endian in native code, + // so we would have to flip each Uint16 ourselves. + // At that point, it's just quicker to convert + // in JS code anyways + var ret = ""; + for (var i = 0; i < bytes.length; i += 2) { + ret += String.fromCharCode(bytes[i] << 8 | bytes[i+1]); + } + + return ret; }; - + UTF16BE.prototype.mapToNative = function(str) { - // nodejs can't convert big-endian in native code, - // so we would have to flip each Uint16 ourselves. - // At that point, it's just quicker to convert - // in JS code anyways - var ret = new Uint8Array(str.length * 2 + 2); - var c; - for (var i = 0; i < str.length; i++) { - c = str.charCodeAt(i); - ret[i*2] = (c >> 8) & 0xFF; - ret[i*2+1] = c & 0xFF; - } - // double null terminate it, just in case - ret[i*2+1] = 0; - ret[i*2+2] = 0; - - return ret; + // nodejs can't convert big-endian in native code, + // so we would have to flip each Uint16 ourselves. + // At that point, it's just quicker to convert + // in JS code anyways + var ret = new Uint8Array(str.length * 2 + 2); + var c; + for (var i = 0; i < str.length; i++) { + c = str.charCodeAt(i); + ret[i*2] = (c >> 8) & 0xFF; + ret[i*2+1] = c & 0xFF; + } + // double null terminate it, just in case + ret[i*2+1] = 0; + ret[i*2+2] = 0; + + return ret; }; Charmap._algorithms["UTF-16BE"] = UTF16BE; diff --git a/js/lib/UTF16LE.js b/js/lib/UTF16LE.js index a3597ef7e8..e58f3680b9 100644 --- a/js/lib/UTF16LE.js +++ b/js/lib/UTF16LE.js @@ -1,7 +1,7 @@ /* - * UTF16LE.js - Implement Unicode Transformation Format 16 bit, + * UTF16LE.js - Implement Unicode Transformation Format 16 bit, * Little Endian mappings - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -64,39 +64,39 @@ UTF16LE.prototype._init = function(options) { }; UTF16LE.prototype.mapToUnicode = function (bytes) { - if (typeof(Buffer) !== "undefined") { - // nodejs can convert it quickly in native code - var b = new Buffer(bytes); - return b.toString("utf16le"); - } - // otherwise we have to implement it in pure JS - var ret = ""; - for (var i = 0; i < bytes.length; i += 2) { - ret += String.fromCharCode(bytes[i+1] << 8 | bytes[i]); - } - - return ret; + if (typeof(Buffer) !== "undefined") { + // nodejs can convert it quickly in native code + var b = new Buffer(bytes); + return b.toString("utf16le"); + } + // otherwise we have to implement it in pure JS + var ret = ""; + for (var i = 0; i < bytes.length; i += 2) { + ret += String.fromCharCode(bytes[i+1] << 8 | bytes[i]); + } + + return ret; }; - + UTF16LE.prototype.mapToNative = function(str) { - if (typeof(Buffer) !== "undefined") { - // nodejs can convert it quickly in native code - var b = new Buffer(str, "utf16le"); - return new Uint8Array(b); - } - // otherwise we have to implement it in pure JS - var ret = new Uint8Array(str.length * 2 + 2); - var c; - for (var i = 0; i < str.length; i++) { - c = str.charCodeAt(i); - ret[i*2] = c & 0xFF; - ret[i*2+1] = (c >> 8) & 0xFF; - } - // double null terminate it, just in case - ret[i*2+1] = 0; - ret[i*2+2] = 0; - - return ret; + if (typeof(Buffer) !== "undefined") { + // nodejs can convert it quickly in native code + var b = new Buffer(str, "utf16le"); + return new Uint8Array(b); + } + // otherwise we have to implement it in pure JS + var ret = new Uint8Array(str.length * 2 + 2); + var c; + for (var i = 0; i < str.length; i++) { + c = str.charCodeAt(i); + ret[i*2] = c & 0xFF; + ret[i*2+1] = (c >> 8) & 0xFF; + } + // double null terminate it, just in case + ret[i*2+1] = 0; + ret[i*2+2] = 0; + + return ret; }; Charmap._algorithms["UTF-16"] = UTF16LE; diff --git a/js/lib/UTF8.js b/js/lib/UTF8.js index 826488c06f..cd5c1bea0f 100644 --- a/js/lib/UTF8.js +++ b/js/lib/UTF8.js @@ -1,6 +1,6 @@ /* * UTF8.js - Implement Unicode Transformation Format 8-bit mappings - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -64,138 +64,138 @@ UTF8.prototype._init = function(options) { }; UTF8.prototype.validate = function(bytes) { - var i = 0; - while (i < bytes.length) { - if ((bytes[i] & 0x80) === 0) { - i++; - } else { - var len; - if ((bytes[i] & 0xC0) === 0xC0) { - len = 2; - } else if ((bytes[i] & 0xE0) === 0xE0) { - len = 3; - } else if ((bytes[i] & 0xF0) === 0xF0) { - len = 4; - } else { - // invalid lead byte - return false; - } - if (i + len > bytes.length) { - // not enough trailing bytes - return false; - } - for (var j = 1; j < len; j++) { - // check each trailing byte to see if it has the correct form - if ((bytes[i+j] & 0x80) !== 0x80) { - return false; - } - } - i += len; - } - } - - return true; + var i = 0; + while (i < bytes.length) { + if ((bytes[i] & 0x80) === 0) { + i++; + } else { + var len; + if ((bytes[i] & 0xC0) === 0xC0) { + len = 2; + } else if ((bytes[i] & 0xE0) === 0xE0) { + len = 3; + } else if ((bytes[i] & 0xF0) === 0xF0) { + len = 4; + } else { + // invalid lead byte + return false; + } + if (i + len > bytes.length) { + // not enough trailing bytes + return false; + } + for (var j = 1; j < len; j++) { + // check each trailing byte to see if it has the correct form + if ((bytes[i+j] & 0x80) !== 0x80) { + return false; + } + } + i += len; + } + } + + return true; }; - + UTF8.prototype.mapToUnicode = function (bytes) { - if (typeof(Buffer) !== "undefined") { - // nodejs can convert it quickly in native code - var b = new Buffer(bytes); - return b.toString("utf8"); - } - // otherwise we have to implement it in pure JS - var ret = ""; - var i = 0; - while (i < bytes.length) { - if (bytes[i] === 0) { - // null-terminator - i = bytes.length; - } else if ((bytes[i] & 0x80) === 0) { - // 1 byte char - ret += String.fromCharCode(bytes[i++]); - } else if ((bytes[i] & 0xE0) === 0xC0) { - // 2 byte char - if (i + 1 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80) { - throw "invalid utf-8 bytes"; - } - // xxx xxyyyyyy - ret += String.fromCharCode((bytes[i] & 0x1F) << 6 | (bytes[i+1] & 0x3F)); - i += 2; - } else if ((bytes[i] & 0xF0) === 0xE0) { - // 3 byte char - if (i + 2 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80) { - throw "invalid utf-8 bytes"; - } - // xxxxyyyy yyzzzzzz - ret += String.fromCharCode((bytes[i] & 0xF) << 12 | (bytes[i+1] & 0x3F) << 6 | (bytes[i+2] & 0x3F)); - i += 3; - } else if ((bytes[i] & 0xF8) === 0xF0) { - // 4 byte char - if (i + 3 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80 || (bytes[i+3] & 0x80) !== 0x80) { - throw "invalid utf-8 bytes"; - } - // wwwxx xxxxyyyy yyzzzzzz - ret += IString.fromCodePoint((bytes[i] & 0x7) << 18 | (bytes[i+1] & 0x3F) << 12 | (bytes[i+2] & 0x3F) << 6 | (bytes[i+3] & 0x3F)); - i += 4; - } else { - throw "invalid utf-8 bytes"; - } - } - - return ret; + if (typeof(Buffer) !== "undefined") { + // nodejs can convert it quickly in native code + var b = new Buffer(bytes); + return b.toString("utf8"); + } + // otherwise we have to implement it in pure JS + var ret = ""; + var i = 0; + while (i < bytes.length) { + if (bytes[i] === 0) { + // null-terminator + i = bytes.length; + } else if ((bytes[i] & 0x80) === 0) { + // 1 byte char + ret += String.fromCharCode(bytes[i++]); + } else if ((bytes[i] & 0xE0) === 0xC0) { + // 2 byte char + if (i + 1 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80) { + throw "invalid utf-8 bytes"; + } + // xxx xxyyyyyy + ret += String.fromCharCode((bytes[i] & 0x1F) << 6 | (bytes[i+1] & 0x3F)); + i += 2; + } else if ((bytes[i] & 0xF0) === 0xE0) { + // 3 byte char + if (i + 2 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80) { + throw "invalid utf-8 bytes"; + } + // xxxxyyyy yyzzzzzz + ret += String.fromCharCode((bytes[i] & 0xF) << 12 | (bytes[i+1] & 0x3F) << 6 | (bytes[i+2] & 0x3F)); + i += 3; + } else if ((bytes[i] & 0xF8) === 0xF0) { + // 4 byte char + if (i + 3 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80 || (bytes[i+3] & 0x80) !== 0x80) { + throw "invalid utf-8 bytes"; + } + // wwwxx xxxxyyyy yyzzzzzz + ret += IString.fromCodePoint((bytes[i] & 0x7) << 18 | (bytes[i+1] & 0x3F) << 12 | (bytes[i+2] & 0x3F) << 6 | (bytes[i+3] & 0x3F)); + i += 4; + } else { + throw "invalid utf-8 bytes"; + } + } + + return ret; }; - + UTF8.prototype.mapToNative = function(str) { - if (typeof(Buffer) !== "undefined") { - // nodejs can convert it quickly in native code - var b = new Buffer(str, "utf8"); - return new Uint8Array(b); - } - // otherwise we have to implement it in pure JS - var istr = (str instanceof IString) ? str : new IString(str); - - // step through the surrogate pairs as single code points by using - // IString's iterator - var it = istr.iterator(); - - // multiply by 4 because the max size of a UTF-8 char is 4 bytes, so - // this will at least get us enough room to encode everything. Add 1 - // for the null terminator - var ret = new Uint8Array(istr.length * 4 + 1); - var i = 0; - - while (it.hasNext()) { - var c = it.next(); - if (c > 0x7F) { - if (c > 0x7FF) { - if (c > 0xFFFF) { - // astral planes char - ret[i] = 0xF0 | ((c >> 18) & 0x3); - ret[i+1] = 0x80 | ((c >> 12) & 0x3F); - ret[i+2] = 0x80 | ((c >> 6) & 0x3F); - ret[i+3] = 0x80 | (c & 0x3F); - - i += 4; - } else { - ret[i] = 0xE0 | ((c >> 12) & 0xF); - ret[i+1] = 0x80 | ((c >> 6) & 0x3F); - ret[i+2] = 0x80 | (c & 0x3F); - - i += 3; - } - } else { - ret[i] = 0xC0 | ((c >> 6) & 0x1F); - ret[i+1] = 0x80 | (c & 0x3F); - - i += 2; - } - } else { - ret[i++] = (c & 0x7F); - } - } - ret[i] = 0; // null-terminate it - - return ret; + if (typeof(Buffer) !== "undefined") { + // nodejs can convert it quickly in native code + var b = new Buffer(str, "utf8"); + return new Uint8Array(b); + } + // otherwise we have to implement it in pure JS + var istr = (str instanceof IString) ? str : new IString(str); + + // step through the surrogate pairs as single code points by using + // IString's iterator + var it = istr.iterator(); + + // multiply by 4 because the max size of a UTF-8 char is 4 bytes, so + // this will at least get us enough room to encode everything. Add 1 + // for the null terminator + var ret = new Uint8Array(istr.length * 4 + 1); + var i = 0; + + while (it.hasNext()) { + var c = it.next(); + if (c > 0x7F) { + if (c > 0x7FF) { + if (c > 0xFFFF) { + // astral planes char + ret[i] = 0xF0 | ((c >> 18) & 0x3); + ret[i+1] = 0x80 | ((c >> 12) & 0x3F); + ret[i+2] = 0x80 | ((c >> 6) & 0x3F); + ret[i+3] = 0x80 | (c & 0x3F); + + i += 4; + } else { + ret[i] = 0xE0 | ((c >> 12) & 0xF); + ret[i+1] = 0x80 | ((c >> 6) & 0x3F); + ret[i+2] = 0x80 | (c & 0x3F); + + i += 3; + } + } else { + ret[i] = 0xC0 | ((c >> 6) & 0x1F); + ret[i+1] = 0x80 | (c & 0x3F); + + i += 2; + } + } else { + ret[i++] = (c & 0x7F); + } + } + ret[i] = 0; // null-terminate it + + return ret; }; Charmap._algorithms["UTF-8"] = UTF8; diff --git a/js/lib/UnitFmt.js b/js/lib/UnitFmt.js index aef11380c8..d56e447360 100644 --- a/js/lib/UnitFmt.js +++ b/js/lib/UnitFmt.js @@ -30,7 +30,7 @@ Measurement.js // !data unitfmt -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); diff --git a/js/lib/UnknownUnit.js b/js/lib/UnknownUnit.js index 71a895451e..3288ca1209 100644 --- a/js/lib/UnknownUnit.js +++ b/js/lib/UnknownUnit.js @@ -59,7 +59,7 @@ UnknownUnit.systems = { }; UnknownUnit.aliases = { - "unknown":"unknown" + "unknown":"unknown" }; /** @@ -75,7 +75,7 @@ UnknownUnit.aliases = { * @return {string} the name of the type of this measurement */ UnknownUnit.prototype.getMeasure = function() { - return "unknown"; + return "unknown"; }; /** @@ -89,7 +89,7 @@ UnknownUnit.prototype.getMeasure = function() { * measurement type */ UnknownUnit.prototype.convert = function(to) { - return undefined; + return undefined; }; /** @@ -164,7 +164,7 @@ UnknownUnit.prototype.expand = function(measurementsystem) { * @static */ UnknownUnit.getMeasures = function () { - return []; + return []; }; module.exports = UnknownUnit; diff --git a/js/lib/Utils.js b/js/lib/Utils.js index d278ed46c1..93b3f6a5f4 100644 --- a/js/lib/Utils.js +++ b/js/lib/Utils.js @@ -1,6 +1,6 @@ /* * Utils.js - Core utility routines - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -157,9 +157,9 @@ Utils.getSublocales = function(locale) { /** * Find and merge all the locale data for a particular prefix in the given locale - * and return it as a single javascript object. This merges the data in the + * and return it as a single javascript object. This merges the data in the * correct order: - * + * *
      *
    1. shared data (usually English) *
    2. data for language @@ -167,10 +167,10 @@ Utils.getSublocales = function(locale) { *
    3. data for language + region + script *
    4. data for language + region + script + variant *
    - * - * It is okay for any of the above to be missing. This function will just skip the + * + * It is okay for any of the above to be missing. This function will just skip the * missing data. - * + * * @static * @param {string} prefix prefix under ilib.data of the data to merge * @param {Locale} locale locale of the data being sought @@ -205,53 +205,53 @@ Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) { /** * Return an array of relative path names for the * files that represent the data for the given locale.

    - * + * * Note that to prevent the situation where a directory for * a language exists next to the directory for a region where - * the language code and region code differ only by case, the - * plain region directories are located under the special + * the language code and region code differ only by case, the + * plain region directories are located under the special * "undefined" language directory which has the ISO code "und". - * The reason is that some platforms have case-insensitive - * file systems, and you cannot have 2 directories with the + * The reason is that some platforms have case-insensitive + * file systems, and you cannot have 2 directories with the * same name which only differ by case. For example, "es" is * the ISO 639 code for the language "Spanish" and "ES" is * the ISO 3166 code for the region "Spain", so both the * directories cannot exist underneath "locale". The region - * therefore will be loaded from "und/ES" instead.

    - * + * therefore will be loaded from "und/ES" instead.

    + * *

    Variations

    - * + * * With only language and region specified, the following * sequence of paths will be generated:

    - * + * *

      * language
      * und/region
      * language/region
      * 
    - * + * * With only language and script specified:

    - * + * *

      * language
      * language/script
      * 
    - * + * * With only script and region specified:

    - * + * *

    - * und/region  
    + * und/region
      * 
    - * + * * With only region and variant specified:

    - * + * *

      * und/region
      * region/variant
      * 
    - * + * * With only language, script, and region specified:

    - * + * *

      * language
      * und/region
    @@ -259,9 +259,9 @@ Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) {
      * language/region
      * language/script/region
      * 
    - * + * * With only language, region, and variant specified:

    - * + * *

      * language
      * und/region
    @@ -269,9 +269,9 @@ Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) {
      * region/variant
      * language/region/variant
      * 
    - * + * * With all parts specified:

    - * + * *

      * language
      * und/region
    @@ -282,7 +282,7 @@ Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) {
      * language/region/variant
      * language/script/region/variant
      * 
    - * + * * @static * @param {Locale} locale load the files for this locale * @param {string?} name the file name of each file to load without @@ -305,17 +305,17 @@ Utils.getLocFiles = function(locale, name) { * @private */ Utils._callLoadData = function (files, sync, params, callback) { - // console.log("Utils._callLoadData called"); - if (typeof(ilib._load) === 'function') { - // console.log("Utils._callLoadData: calling as a regular function"); - return ilib._load(files, sync, params, callback); - } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { - // console.log("Utils._callLoadData: calling as an object"); - return ilib._load.loadFiles(files, sync, params, callback); - } - - // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); - return undefined; + // console.log("Utils._callLoadData called"); + if (typeof(ilib._load) === 'function') { + // console.log("Utils._callLoadData: calling as a regular function"); + return ilib._load(files, sync, params, callback); + } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { + // console.log("Utils._callLoadData: calling as an object"); + return ilib._load.loadFiles(files, sync, params, callback); + } + + // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); + return undefined; }; /** diff --git a/js/lib/WebLoader.js b/js/lib/WebLoader.js index c9d41209f8..0a8e82013c 100644 --- a/js/lib/WebLoader.js +++ b/js/lib/WebLoader.js @@ -1,6 +1,6 @@ /* - * WebLoader.js - loader implementation for web apps. - * + * WebLoader.js - loader implementation for web apps. + * * Copyright © 2015-2016, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,55 +26,55 @@ var Locale = require("./Locale.js"); /** * @class * An implementation of the Loader class to load locale web pages. - * + * * @constructor * @private */ var WebLoader = function(ilib, sync, onLoad) { - //console.log("new WebLoader instance\n"); - - this.parent.call(this, ilib); - - this._loadFile = (navigator.userAgent.indexOf(" .NET") > -1) ? this._ieLoadFile : this._regularLoadFile; - - // for use from within a check-out of ilib - var base, root, pos, colon; - - var scripts = document.getElementsByTagName("script"); - - pos = window.location.href.lastIndexOf("html"); - this.root = window.location.href.substring(0, pos); - pos = this.root.lastIndexOf("/"); - this.root = this.root.substring(0, pos); - - colon = this.root.indexOf('://'); - this.root = Path.normalize(Path.join(this.root.substring(colon+3))); - - for (var i = 0; i < scripts.length; i++) { - var source = scripts[i].src; - if (source && (pos = source.search(/\/ilib-[a-z\-]*\.js$/)) !== -1) { - colon = source.indexOf('://'); - this.protocol = source.substring(0,colon+3); - base = Path.join(source.substring(colon+3, pos-1), ".."); - break; - } - } - - this.base = Path.normalize(Path.join(base || this.root, "data")); - - //console.log("WebLoader.constructor: this.base is " + this.base); - //console.log("WebLoader.constructor: this.root is " + this.root); - - this.includePath.push(Path.join(this.root, "resources")); // always check the application's resources dir first - - // then a standard locale dir of a built version of ilib - this._exists(Path.join(base, "locale"), "localeinfo.json"); - - // then try the standard install directories - this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); - - // if all else fails, try a check-out dir of ilib - // this._exists(Path.join(this.base, "locale"), "localeinfo.json"); + //console.log("new WebLoader instance\n"); + + this.parent.call(this, ilib); + + this._loadFile = (navigator.userAgent.indexOf(" .NET") > -1) ? this._ieLoadFile : this._regularLoadFile; + + // for use from within a check-out of ilib + var base, root, pos, colon; + + var scripts = document.getElementsByTagName("script"); + + pos = window.location.href.lastIndexOf("html"); + this.root = window.location.href.substring(0, pos); + pos = this.root.lastIndexOf("/"); + this.root = this.root.substring(0, pos); + + colon = this.root.indexOf('://'); + this.root = Path.normalize(Path.join(this.root.substring(colon+3))); + + for (var i = 0; i < scripts.length; i++) { + var source = scripts[i].src; + if (source && (pos = source.search(/\/ilib-[a-z\-]*\.js$/)) !== -1) { + colon = source.indexOf('://'); + this.protocol = source.substring(0,colon+3); + base = Path.join(source.substring(colon+3, pos-1), ".."); + break; + } + } + + this.base = Path.normalize(Path.join(base || this.root, "data")); + + //console.log("WebLoader.constructor: this.base is " + this.base); + //console.log("WebLoader.constructor: this.root is " + this.root); + + this.includePath.push(Path.join(this.root, "resources")); // always check the application's resources dir first + + // then a standard locale dir of a built version of ilib + this._exists(Path.join(base, "locale"), "localeinfo.json"); + + // then try the standard install directories + this._exists("/usr/share/javascript/ilib/locale", "localeinfo.json"); + + // if all else fails, try a check-out dir of ilib + // this._exists(Path.join(this.base, "locale"), "localeinfo.json"); }; WebLoader.prototype = new Loader(); @@ -83,144 +83,144 @@ WebLoader.prototype.constructor = WebLoader; WebLoader.prototype.name = "WebLoader"; WebLoader.prototype._ieLoadFile = function(pathname, sync, cb) { - // special case for IE because it has a @#$%ed up XMLHttpRequest implementation - var req = new ActiveXObject("MSXML2.XMLHTTP"); - var text = undefined; - - req.open("GET", this.protocol + pathname, !sync); - - if (!sync) { - req.onreadystatechange = function() { - if (req.readyState === 4) { - text = req.responseText; - if (typeof(cb) === 'function') { - cb(text); - } - } - }; - } - - try { - req.send(); - - text = req.responseText; - } catch (e) { - text = undefined; - } - if (sync) { - if (typeof(cb) === 'function') { - cb(text); - } - } - - return text; + // special case for IE because it has a @#$%ed up XMLHttpRequest implementation + var req = new ActiveXObject("MSXML2.XMLHTTP"); + var text = undefined; + + req.open("GET", this.protocol + pathname, !sync); + + if (!sync) { + req.onreadystatechange = function() { + if (req.readyState === 4) { + text = req.responseText; + if (typeof(cb) === 'function') { + cb(text); + } + } + }; + } + + try { + req.send(); + + text = req.responseText; + } catch (e) { + text = undefined; + } + if (sync) { + if (typeof(cb) === 'function') { + cb(text); + } + } + + return text; }; WebLoader.prototype._regularLoadFile = function (pathname, sync, cb) { - // use normal web techniques - var req = new XMLHttpRequest(); - var text = undefined; - - //req.open("GET", "file:" + Path.resolve(file), false); - if (pathname.substring(0, this.protocol.length) !== this.protocol) { - pathname = this.protocol + pathname; - } - req.open("GET", pathname, !sync); - //req.responseType = "text"; - req.onload = function(e) { - text = req.response; - if (typeof(cb) === 'function') { - cb((req.status === 0 || req.status === 200) ? text : undefined); - } - }; - req.onerror = function(err) { - // file is not there or could not be loaded - text = undefined; - if (typeof(cb) === 'function') { - cb(undefined); - } - }; - - //console.log("url is " + JSON.stringify(req._url, undefined, 4)); - try { - req.send(); - } catch (e) { - // could not load the file - text = undefined; - if (typeof(cb) === 'function') { - cb(undefined); - } - } - - return text; + // use normal web techniques + var req = new XMLHttpRequest(); + var text = undefined; + + //req.open("GET", "file:" + Path.resolve(file), false); + if (pathname.substring(0, this.protocol.length) !== this.protocol) { + pathname = this.protocol + pathname; + } + req.open("GET", pathname, !sync); + //req.responseType = "text"; + req.onload = function(e) { + text = req.response; + if (typeof(cb) === 'function') { + cb((req.status === 0 || req.status === 200) ? text : undefined); + } + }; + req.onerror = function(err) { + // file is not there or could not be loaded + text = undefined; + if (typeof(cb) === 'function') { + cb(undefined); + } + }; + + //console.log("url is " + JSON.stringify(req._url, undefined, 4)); + try { + req.send(); + } catch (e) { + // could not load the file + text = undefined; + if (typeof(cb) === 'function') { + cb(undefined); + } + } + + return text; }; WebLoader.prototype.getProperPath = function(params) { - var loc, language, script, region; - var isExist, returnPath, dir; - var baseResDir = "resources"; - - if (!params.name) { - return undefined; - } - - if (params) { - if (params.locale){ - this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; - } - - if (params.name) { - this.name = params.name; - } - - if (params.baseResDir) { - baseResDir = params.baseResDir; - } - - loc = this.locale || new Locale(); - - language = loc.getLanguage(); - script = loc.getScript(); - region = loc.getRegion(); - - this.listAvailableFiles(); - - if (language) { - dir = language +"/" + this.name; - isExist = this.checkAvailability(dir); - if (isExist) { - returnPath = dir; - } - } - - if (script) { - dir = language +"/" + script + "/" + this.name; - isExist = this.checkAvailability(dir); - if (isExist) { - returnPath = dir; - } - } - - if (region) { - if (script) { - dir = language +"/" + script + "/" + region + "/" + this.name; - } else { - dir = language +"/" + region + "/" + this.name; - } - - isExist = this.checkAvailability(dir); - if (isExist) { - returnPath = dir; - } - } - - if (!returnPath) { - return this.name; - } - - } else { - return undefined; - } - return baseResDir + "/" + returnPath; + var loc, language, script, region; + var isExist, returnPath, dir; + var baseResDir = "resources"; + + if (!params.name) { + return undefined; + } + + if (params) { + if (params.locale){ + this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; + } + + if (params.name) { + this.name = params.name; + } + + if (params.baseResDir) { + baseResDir = params.baseResDir; + } + + loc = this.locale || new Locale(); + + language = loc.getLanguage(); + script = loc.getScript(); + region = loc.getRegion(); + + this.listAvailableFiles(); + + if (language) { + dir = language +"/" + this.name; + isExist = this.checkAvailability(dir); + if (isExist) { + returnPath = dir; + } + } + + if (script) { + dir = language +"/" + script + "/" + this.name; + isExist = this.checkAvailability(dir); + if (isExist) { + returnPath = dir; + } + } + + if (region) { + if (script) { + dir = language +"/" + script + "/" + region + "/" + this.name; + } else { + dir = language +"/" + region + "/" + this.name; + } + + isExist = this.checkAvailability(dir); + if (isExist) { + returnPath = dir; + } + } + + if (!returnPath) { + return this.name; + } + + } else { + return undefined; + } + return baseResDir + "/" + returnPath; } module.exports = WebLoader; diff --git a/js/lib/WebpackLoader.js b/js/lib/WebpackLoader.js index c46d5cee39..4b1ff9018f 100644 --- a/js/lib/WebpackLoader.js +++ b/js/lib/WebpackLoader.js @@ -91,7 +91,7 @@ module.exports = function (ilib) { } filename = locale && locale.getSpec() || (base === "ilibmanifest" ? "localmanifest" : "root"); - + var dataName = base; if (dir) { if (locale) { diff --git a/js/lib/datefmtstr.js b/js/lib/datefmtstr.js index da922a1e22..2686ee9a41 100644 --- a/js/lib/datefmtstr.js +++ b/js/lib/datefmtstr.js @@ -1,6 +1,6 @@ /* * datefmtstr.js - Date formatter strings - * + * * Copyright © 2012-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,8 @@ * * See the License for the specific language governing permissions and * limitations under the License. - * - * This file is not used with ilib. Its purpose is to list strings that can be + * + * This file is not used with ilib. Its purpose is to list strings that can be * extracted with the localization tool for the purpose of localizing them. The * strings are looked up via dynamically generated keys, so the localization tool has * no way of finding and extracting them properly without this file. diff --git a/js/lib/externs.js b/js/lib/externs.js index b0c00da54b..9d7aaf24ba 100644 --- a/js/lib/externs.js +++ b/js/lib/externs.js @@ -1,6 +1,6 @@ /* * externs.js - define externs for the google closure compiler - * + * * Copyright © 2012-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/js/lib/ilib-getdata.js b/js/lib/ilib-getdata.js index fe7bf518f2..84c3620d76 100644 --- a/js/lib/ilib-getdata.js +++ b/js/lib/ilib-getdata.js @@ -1,6 +1,6 @@ /** * ilib-getdata.js - define the locale data for assembled or dynamic - * + * * @license * Copyright © 2018, JEDLSoft * @@ -20,7 +20,7 @@ var ilib = require("./ilib.js"); -// The following will either require and then install the +// The following will either require and then install the // WebpackLoader to dynamically load locale data bundles, // or it will statically require all of the locale data that // this build needs so that it can be included into this diff --git a/js/lib/ilib-node-assembled.js b/js/lib/ilib-node-assembled.js index f3fd8dc9ed..357e82f679 100644 --- a/js/lib/ilib-node-assembled.js +++ b/js/lib/ilib-node-assembled.js @@ -2,7 +2,7 @@ /* * ilib-node-assembled.js - glue code for node to load it the old way * as one big assembled file - * + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/js/lib/ilib-node-async.js b/js/lib/ilib-node-async.js index b962232e36..0c7d356590 100644 --- a/js/lib/ilib-node-async.js +++ b/js/lib/ilib-node-async.js @@ -1,9 +1,9 @@ /* jshint node: true */ /* - * ilib-node-async.js - glue code for node to load local ilib code and + * ilib-node-async.js - glue code for node to load local ilib code and * data dynamically and asynchronously. Intended to be used with the * unit tests. - * + * * Copyright © 2016, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/js/lib/ilib-node.js b/js/lib/ilib-node.js index d87351841e..a902d5699e 100644 --- a/js/lib/ilib-node.js +++ b/js/lib/ilib-node.js @@ -1,8 +1,8 @@ /* jshint node: true */ /* - * ilib-node.js - glue code for node to load local ilib code and - * data dynamically - * + * ilib-node.js - glue code for node to load local ilib code and + * data dynamically + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/js/lib/ilib-qt.js b/js/lib/ilib-qt.js index 0d023dc1a4..d80f630e3b 100644 --- a/js/lib/ilib-qt.js +++ b/js/lib/ilib-qt.js @@ -1,6 +1,6 @@ /* - * ilib-qt.js - glue code for qt apps to load local ilib code and data - * + * ilib-qt.js - glue code for qt apps to load local ilib code and data + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,52 +18,52 @@ */ var module = { - exports: {}, - filename:null + exports: {}, + filename:null }; var requireClass = function() { - this.cache = {}; - this.loading = {}; - this.updateRequire = /require\(("[^/][^"+]*")\)/g; - - this.root = Qt.resolvedUrl(".").toString(); - if (this.root.substring(0,7) === "file://") { - this.root = this.root.substring(7); - } - if (this.root[this.root.length-1] === '/') { - this.root = this.normalize(this.root.substring(0,this.root.length-1)); - } + this.cache = {}; + this.loading = {}; + this.updateRequire = /require\(("[^/][^"+]*")\)/g; + + this.root = Qt.resolvedUrl(".").toString(); + if (this.root.substring(0,7) === "file://") { + this.root = this.root.substring(7); + } + if (this.root[this.root.length-1] === '/') { + this.root = this.normalize(this.root.substring(0,this.root.length-1)); + } }; requireClass.prototype.dirname = function(pathname) { - pathname = pathname.replace("\\", "/"); - var i = pathname.lastIndexOf("/"); - return i !== -1 ? pathname.substring(0,i) : pathname; + pathname = pathname.replace("\\", "/"); + var i = pathname.lastIndexOf("/"); + return i !== -1 ? pathname.substring(0,i) : pathname; }; requireClass.prototype.normalize = function(pathname) { - if (pathname) { - var previousLen; - pathname = pathname.replace(/\\/g, "/"); - do { - previousLen = pathname.length; - pathname = pathname.replace(/\/\//g, "/"); - pathname = pathname.replace(/\/[^/]*[^\./]\/\.\./g, "/."); - pathname = pathname.replace(/\/\.\//g, "/"); - pathname = pathname.replace(/^\.\//, ""); - pathname = pathname.replace(/\/\.$/, "/"); - if (pathname.length > 1) pathname = pathname.replace(/\/$/, ""); - if (pathname.length === 0) pathname = '.'; - } while (pathname.length < previousLen); - } - return pathname; + if (pathname) { + var previousLen; + pathname = pathname.replace(/\\/g, "/"); + do { + previousLen = pathname.length; + pathname = pathname.replace(/\/\//g, "/"); + pathname = pathname.replace(/\/[^/]*[^\./]\/\.\./g, "/."); + pathname = pathname.replace(/\/\.\//g, "/"); + pathname = pathname.replace(/^\.\//, ""); + pathname = pathname.replace(/\/\.$/, "/"); + if (pathname.length > 1) pathname = pathname.replace(/\/$/, ""); + if (pathname.length === 0) pathname = '.'; + } while (pathname.length < previousLen); + } + return pathname; }; - + requireClass.prototype.require = function(parent, pathname, absolutePath) { //console.log("------------------------\nrequire: called with " + pathname); - if (pathname === "./TestSuiteModule.js") { - // special case to redirect to qt instead + if (pathname === "./TestSuiteModule.js") { + // special case to redirect to qt instead pathname = this.root + "/../../qt/NodeunitTest/TestSuiteModule.js"; } else if (pathname === "nodeunit") { //console.log(" [ilib-qt.js] Loading nodeunit-qml.js "); @@ -72,64 +72,64 @@ requireClass.prototype.require = function(parent, pathname, absolutePath) { pathname = this.root + "/../test" + absolutePath; //console.log("[ilib-qt.js] Loading Test file... "+ pathname); } else { - - if (parent && parent.charAt(0) !== '/') { - // take care of relative parents (aren't all parents relatives? haha) - parent = this.root + '/' + parent; - } - - //console.log("this.root is " + this.root + " and pathname before was " + pathname); - //console.log("require: module.filename is " + module.filename); - //console.log("require: parent is " + parent); - - var base = parent || (module.filename && this.dirname(module.filename)) || this.root; - - //console.log("require: base is " + base); - - if (pathname.charAt(0) !== '/') { - pathname = base + "/" + pathname; - } - } - - pathname = this.normalize(pathname); + + if (parent && parent.charAt(0) !== '/') { + // take care of relative parents (aren't all parents relatives? haha) + parent = this.root + '/' + parent; + } + + //console.log("this.root is " + this.root + " and pathname before was " + pathname); + //console.log("require: module.filename is " + module.filename); + //console.log("require: parent is " + parent); + + var base = parent || (module.filename && this.dirname(module.filename)) || this.root; + + //console.log("require: base is " + base); + + if (pathname.charAt(0) !== '/') { + pathname = base + "/" + pathname; + } + } + + pathname = this.normalize(pathname); //console.log(" require: pathname after is " + pathname); - - if (this.cache[pathname]) { - //console.log("require: cache hit"); - return this.cache[pathname]; - } - - // don't try to load things that are currently in the process of loading - if (this.loading[pathname]) { - //console.log("require: already loading..."); - return {}; - } - //console.log("require: loading the file"); - - // communicate the current dir to the included js file - var tmp = module.filename; - module.filename = pathname; - this.loading[pathname] = true; - module.require = requireClass.prototype.require.bind(r, this.dirname(pathname)); - - var s = Qt.include(pathname); - - module.filename = tmp; - this.loading[pathname] = undefined; - - if (s.status === s.OK) { - module.exports.module = { - filename: pathname, - require: requireClass.prototype.require.bind(r, this.dirname(pathname)) - }; - this.cache[pathname] = module.exports; - return module.exports; - } - - console.log("exception was " + JSON.stringify(s.status, undefined, 4)); - console.log("Failed loading " + pathname); - console.trace(); - return undefined; + + if (this.cache[pathname]) { + //console.log("require: cache hit"); + return this.cache[pathname]; + } + + // don't try to load things that are currently in the process of loading + if (this.loading[pathname]) { + //console.log("require: already loading..."); + return {}; + } + //console.log("require: loading the file"); + + // communicate the current dir to the included js file + var tmp = module.filename; + module.filename = pathname; + this.loading[pathname] = true; + module.require = requireClass.prototype.require.bind(r, this.dirname(pathname)); + + var s = Qt.include(pathname); + + module.filename = tmp; + this.loading[pathname] = undefined; + + if (s.status === s.OK) { + module.exports.module = { + filename: pathname, + require: requireClass.prototype.require.bind(r, this.dirname(pathname)) + }; + this.cache[pathname] = module.exports; + return module.exports; + } + + console.log("exception was " + JSON.stringify(s.status, undefined, 4)); + console.log("Failed loading " + pathname); + console.trace(); + return undefined; }; var r = new requireClass(); diff --git a/js/lib/ilib-rhino.js b/js/lib/ilib-rhino.js index a05652aeeb..bee3da0b15 100644 --- a/js/lib/ilib-rhino.js +++ b/js/lib/ilib-rhino.js @@ -1,8 +1,8 @@ /* - * ilib-rhino.js - glue code for rhino apps to load local ilib code and + * ilib-rhino.js - glue code for rhino apps to load local ilib code and * data in a plain rhino environment. If you are using ringojs, use the * ilib-ringo.js loader instead. - * + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,144 +20,144 @@ */ var module = { - exports: {}, - filename:null + exports: {}, + filename:null }; var console = { - log: function() { - print.apply(undefined, arguments); - } + log: function() { + print.apply(undefined, arguments); + } }; console.log("root environment is " + JSON.stringify(environment, undefined, 4)); var requireClass = function() { - this.cache = {}; - this.loading = {}; - this.updateRequire = /require\(("[^/][^"+]*")\)/g; - - this.root = environment["user.dir"]; - if (this.root[this.root.length-1] === '/') { - this.root = this.normalize(this.root.substring(0,this.root.length-1)); - } + this.cache = {}; + this.loading = {}; + this.updateRequire = /require\(("[^/][^"+]*")\)/g; + + this.root = environment["user.dir"]; + if (this.root[this.root.length-1] === '/') { + this.root = this.normalize(this.root.substring(0,this.root.length-1)); + } }; requireClass.prototype.dirname = function(pathname) { - pathname = pathname.replace("\\", "/"); - var i = pathname.lastIndexOf("/"); - return i !== -1 ? pathname.substring(0,i) : pathname; + pathname = pathname.replace("\\", "/"); + var i = pathname.lastIndexOf("/"); + return i !== -1 ? pathname.substring(0,i) : pathname; }; requireClass.prototype.normalize = function(pathname) { - if (pathname) { - var previousLen; - pathname = pathname.replace(/\\/g, "/"); - do { - previousLen = pathname.length; - pathname = pathname.replace(/\/\//g, "/"); - pathname = pathname.replace(/\/[^/]*[^\./]\/\.\./g, "/."); - pathname = pathname.replace(/\/\.\//g, "/"); - pathname = pathname.replace(/^\.\//, ""); - pathname = pathname.replace(/\/\.$/, "/"); - if (pathname.length > 1) pathname = pathname.replace(/\/$/, ""); - if (pathname.length === 0) pathname = '.'; - } while (pathname.length < previousLen); - } - return pathname; + if (pathname) { + var previousLen; + pathname = pathname.replace(/\\/g, "/"); + do { + previousLen = pathname.length; + pathname = pathname.replace(/\/\//g, "/"); + pathname = pathname.replace(/\/[^/]*[^\./]\/\.\./g, "/."); + pathname = pathname.replace(/\/\.\//g, "/"); + pathname = pathname.replace(/^\.\//, ""); + pathname = pathname.replace(/\/\.$/, "/"); + if (pathname.length > 1) pathname = pathname.replace(/\/$/, ""); + if (pathname.length === 0) pathname = '.'; + } while (pathname.length < previousLen); + } + return pathname; }; requireClass.prototype._loadFile = function (pathname) { - console.log("requireClass._loadFile: attempting to load " + pathname); - var text = ""; - var reader; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(pathname), "utf-8")); - var tmp; - while ((tmp = reader.readLine()) !== null) { - text += tmp + '\n'; - } - } catch (e) { - // ignore - text = undefined; - } finally { - if (reader) { - try { - reader.close(); - } catch (e2) {} - } - cb && typeof(cb) === 'function' && cb(text); - } - return text; + console.log("requireClass._loadFile: attempting to load " + pathname); + var text = ""; + var reader; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(pathname), "utf-8")); + var tmp; + while ((tmp = reader.readLine()) !== null) { + text += tmp + '\n'; + } + } catch (e) { + // ignore + text = undefined; + } finally { + if (reader) { + try { + reader.close(); + } catch (e2) {} + } + cb && typeof(cb) === 'function' && cb(text); + } + return text; }; - + requireClass.prototype.require = function(parent, pathname) { - console.log("------------------------\nrequire: called with " + pathname); - - console.log("this.root is " + this.root + " and pathname before was " + pathname); - console.log("require: module.filename is " + module.filename); - console.log("require: parent is " + parent); - - var base = parent || (module.filename && this.dirname(module.filename)) || this.root; - - console.log("require: base is " + base); - - if (pathname.charAt(0) !== '/') { - pathname = base + "/" + pathname; - } - - pathname = this.normalize(pathname); - console.log("require: pathname after is " + pathname); - - if (this.cache[pathname]) { - console.log("require: cache hit"); - return this.cache[pathname]; - } - - // don't try to load things that are currently in the process of loading - if (this.loading[pathname]) { - console.log("require: already loading..."); - return {}; - } - console.log("require: loading the file"); - - try { - var text = this._loadFile(pathname); - var dirname = path.dirname(pathname); - var match, replacement; - - if (text) { - var tmp = module.filename; - module.filename = pathname; - module.exports = null; - this.loading[pathname] = true; - module.require = requireClass.prototype.require.bind(r, this.dirname(pathname)); - - while ((match = this.updateRequire.exec(text)) !== null) { - replacement = path.normalize(path.join(dirname, match[1])); - text = text.replace(new RegExp('"' + match[1] + '"', "g"), '"' + replacement + '"'); - this.updateRequire.lastIndex = match.index + replacement.length + 2; - } - - // console.log("text is " + text); - try { - eval(text); - - this.cache[pathname] = module.exports; - module.exports.module = { - filename: pathname, - require: requireClass.prototype.require.bind(r, this.dirname(pathname)) - }; - } finally { - this.loading[pathname] = undefined; - module.filename = tmp; - } - - return module.exports; - } + console.log("------------------------\nrequire: called with " + pathname); + + console.log("this.root is " + this.root + " and pathname before was " + pathname); + console.log("require: module.filename is " + module.filename); + console.log("require: parent is " + parent); + + var base = parent || (module.filename && this.dirname(module.filename)) || this.root; + + console.log("require: base is " + base); + + if (pathname.charAt(0) !== '/') { + pathname = base + "/" + pathname; + } + + pathname = this.normalize(pathname); + console.log("require: pathname after is " + pathname); + + if (this.cache[pathname]) { + console.log("require: cache hit"); + return this.cache[pathname]; + } + + // don't try to load things that are currently in the process of loading + if (this.loading[pathname]) { + console.log("require: already loading..."); + return {}; + } + console.log("require: loading the file"); + + try { + var text = this._loadFile(pathname); + var dirname = path.dirname(pathname); + var match, replacement; + + if (text) { + var tmp = module.filename; + module.filename = pathname; + module.exports = null; + this.loading[pathname] = true; + module.require = requireClass.prototype.require.bind(r, this.dirname(pathname)); + + while ((match = this.updateRequire.exec(text)) !== null) { + replacement = path.normalize(path.join(dirname, match[1])); + text = text.replace(new RegExp('"' + match[1] + '"', "g"), '"' + replacement + '"'); + this.updateRequire.lastIndex = match.index + replacement.length + 2; + } + + // console.log("text is " + text); + try { + eval(text); + + this.cache[pathname] = module.exports; + module.exports.module = { + filename: pathname, + require: requireClass.prototype.require.bind(r, this.dirname(pathname)) + }; + } finally { + this.loading[pathname] = undefined; + module.filename = tmp; + } + + return module.exports; + } } catch (e) { - console.log("Failed loading " + pathname); - console.log("exception was " + e); + console.log("Failed loading " + pathname); + console.log("exception was " + e); } return undefined; diff --git a/js/lib/ilib-ringo.js b/js/lib/ilib-ringo.js index 258dfc4ec8..63a17acaf7 100644 --- a/js/lib/ilib-ringo.js +++ b/js/lib/ilib-ringo.js @@ -1,7 +1,7 @@ /* * ilib-ringo.js - glue code for rhino apps to load inside of an app server * using ringojs - * + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ * limitations under the License. */ -// ringo already has a CommonJS require system, so we don't have to implement +// ringo already has a CommonJS require system, so we don't have to implement // our own like we do in plain jane rhino var RhinoLoader = require("../lib/RhinoLoader.js"); diff --git a/js/lib/ilib.js b/js/lib/ilib.js index b31b707015..b58ddd1fbd 100644 --- a/js/lib/ilib.js +++ b/js/lib/ilib.js @@ -1,6 +1,6 @@ /* * ilib.js - define the ilib name space - * + * * Copyright © 2012-2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ /** * @namespace The global namespace that contains general ilib functions useful * to all of ilib - * + * * @version // !macro ilibVersion */ var ilib = ilib || {}; @@ -33,7 +33,7 @@ ilib._ver = function() { /** * Return the current version of ilib. - * + * * @static * @return {string} a version string for this instance of ilib */ @@ -54,13 +54,13 @@ ilib.getVersion = function () { * Place where resources and such are eventually assigned. */ ilib.data = { - /** @type {{ccc:Object.,nfd:Object.,nfc:Object.,nfkd:Object.,nfkc:Object.}} */ + /** @type {{ccc:Object.,nfd:Object.,nfc:Object.,nfkd:Object.,nfkc:Object.}} */ norm: { - ccc: {}, - nfd: {}, - nfc: {}, - nfkd: {}, - nfkc: {} + ccc: {}, + nfd: {}, + nfc: {}, + nfkd: {}, + nfkc: {} }, zoneinfo: { "Etc/UTC":{"o":"0:0","f":"UTC"}, @@ -96,15 +96,15 @@ if (typeof(module) !== 'undefined') { * internationalization aspects of software. Instead of translating the text of the software * into a foreign language, as in the process of localization, the textual elements of an application * are replaced with an altered version of the original language.These specific alterations make - * the original words appear readable, but include the most problematic characteristics of + * the original words appear readable, but include the most problematic characteristics of * the world's languages: varying length of text or characters, language direction, and so on. * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF - * + * * @param {string|undefined|null} localename the locale specifier for the pseudo locale */ ilib.setAsPseudoLocale = function (localename) { if (localename) { - ilib.pseudoLocales.push(localename) + ilib.pseudoLocales.push(localename) } }; @@ -113,7 +113,7 @@ ilib.setAsPseudoLocale = function (localename) { * @static */ ilib.clearPseudoLocales = function() { - ilib.pseudoLocales = [ + ilib.pseudoLocales = [ "zxx-XX", "zxx-Cyrl-XX", "zxx-Hans-XX", @@ -131,23 +131,23 @@ ilib.clearPseudoLocales(); */ ilib._getPlatform = function () { if (!ilib._platform) { - try { - if (typeof(java.lang.Object) !== 'undefined') { - ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; - return ilib._platform; - } - } catch (e) {} - + try { + if (typeof(java.lang.Object) !== 'undefined') { + ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; + return ilib._platform; + } + } catch (e) {} + if (typeof(global) !== 'undefined' && global.process && global.process.versions && global.process.versions.node && typeof(module) !== 'undefined') { ilib._platform = "nodejs"; } else if (typeof(Qt) !== 'undefined') { - ilib._platform = "qt"; + ilib._platform = "qt"; } else if (typeof(window) !== 'undefined') { ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; } else { ilib._platform = "unknown"; } - } + } return ilib._platform; }; @@ -155,42 +155,42 @@ ilib._getPlatform = function () { * If this ilib is running in a browser, return the name of that browser. * @private * @static - * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", + * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", * "safari", or "opera"), or undefined if this is not running in a browser or if - * the browser name could not be determined + * the browser name could not be determined */ ilib._getBrowser = function () { - var browser = undefined; - if (ilib._getPlatform() === "browser") { - if (navigator && navigator.userAgent) { - if (navigator.userAgent.indexOf("Firefox") > -1) { - browser = "firefox"; - } - if (navigator.userAgent.search(/Opera|OPR/) > -1 ) { - browser = "opera"; - } - if (navigator.userAgent.indexOf("Chrome") > -1) { - browser = "chrome"; - } - if (navigator.userAgent.indexOf(" .NET") > -1) { - browser = "ie"; - } - if (navigator.userAgent.indexOf("Safari") > -1) { - // chrome also has the string Safari in its userAgent, but the chrome case is - // already taken care of above - browser = "safari"; - } - if (navigator.userAgent.indexOf("Edge") > -1) { + var browser = undefined; + if (ilib._getPlatform() === "browser") { + if (navigator && navigator.userAgent) { + if (navigator.userAgent.indexOf("Firefox") > -1) { + browser = "firefox"; + } + if (navigator.userAgent.search(/Opera|OPR/) > -1 ) { + browser = "opera"; + } + if (navigator.userAgent.indexOf("Chrome") > -1) { + browser = "chrome"; + } + if (navigator.userAgent.indexOf(" .NET") > -1) { + browser = "ie"; + } + if (navigator.userAgent.indexOf("Safari") > -1) { + // chrome also has the string Safari in its userAgent, but the chrome case is + // already taken care of above + browser = "safari"; + } + if (navigator.userAgent.indexOf("Edge") > -1) { browser = "Edge"; } if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) { - // Due to constraints of the iOS platform, + // Due to constraints of the iOS platform, // all browser must be built on top of the WebKit rendering engine browser = "iOS"; } - } - } - return browser; + } + } + return browser; }; /** @@ -225,7 +225,7 @@ ilib._top = function() { }; /** - * Return the value of a global variable given its name in a way that works + * Return the value of a global variable given its name in a way that works * correctly for the current platform. * @private * @static @@ -235,10 +235,10 @@ ilib._top = function() { ilib._global = function(name) { var top = this._top(); try { - return top[name]; - } catch (e) { - return undefined; - } + return top[name]; + } catch (e) { + return undefined; + } }; /** @@ -249,7 +249,7 @@ ilib._global = function(name) { * @return {boolean} true if the global variable is defined on this platform, false otherwise */ ilib._isGlobal = function(name) { - return typeof(ilib._global(name)) !== 'undefined'; + return typeof(ilib._global(name)) !== 'undefined'; }; /** @@ -258,7 +258,7 @@ ilib._isGlobal = function(name) { * for garbage collecting. */ ilib.clearCache = function() { - ilib.data.cache = {}; + ilib.data.cache = {}; }; /** @@ -266,10 +266,10 @@ ilib.clearCache = function() { * when no explicit locale is passed to any ilib class. If the default * locale is not set, ilib will attempt to use the locale of the * environment it is running in, if it can find that. If not, it will - * default to the locale "en-US". If a type of parameter is string, + * default to the locale "en-US". If a type of parameter is string, * ilib will take only well-formed BCP-47 tag

    - * - * + * + * * @static * @param {string|undefined|null} spec the locale specifier for the default locale */ @@ -282,14 +282,14 @@ ilib.setLocale = function (spec) { }; /** - * Return the default locale for all of ilib if one has been set. This - * locale will be used when no explicit locale is passed to any ilib + * Return the default locale for all of ilib if one has been set. This + * locale will be used when no explicit locale is passed to any ilib * class. If the default * locale is not set, ilib will attempt to use the locale of the * environment it is running in, if it can find that. If not, it will * default to the locale "en-US".

    - * - * + * + * * @static * @return {string} the locale specifier for the default locale */ @@ -300,13 +300,13 @@ ilib.getLocale = function () { case 'browser': // running in a browser if(typeof(navigator.language) !== 'undefined') { - ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit + ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit } if (!ilib.locale) { // IE on Windows - lang = typeof(navigator.browserLanguage) !== 'undefined' ? + lang = typeof(navigator.browserLanguage) !== 'undefined' ? navigator.browserLanguage : - (typeof(navigator.userLanguage) !== 'undefined' ? + (typeof(navigator.userLanguage) !== 'undefined' ? navigator.userLanguage : (typeof(navigator.systemLanguage) !== 'undefined' ? navigator.systemLanguage : @@ -319,8 +319,8 @@ ilib.getLocale = function () { break; case 'webos': // webOS - if (typeof(PalmSystem.locales) !== 'undefined' && - typeof(PalmSystem.locales.UI) != 'undefined' && + if (typeof(PalmSystem.locales) !== 'undefined' && + typeof(PalmSystem.locales.UI) != 'undefined' && PalmSystem.locales.UI.length > 0) { ilib.locale = PalmSystem.locales.UI; } else if (typeof(PalmSystem.locale) !== 'undefined') { @@ -376,8 +376,8 @@ ilib.getLocale = function () { * is not set, ilib will attempt to use the time zone of the * environment it is running in, if it can find that. If not, it will * default to the the UTC zone "Etc/UTC".

    - * - * + * + * * @static * @param {string} tz the name of the time zone to set as the default time zone */ @@ -386,14 +386,14 @@ ilib.setTimeZone = function (tz) { }; /** - * Return the default time zone for all of ilib if one has been set. This - * time zone will be used when no explicit time zone is passed to any ilib + * Return the default time zone for all of ilib if one has been set. This + * time zone will be used when no explicit time zone is passed to any ilib * class. If the default time zone * is not set, ilib will attempt to use the locale of the * environment it is running in, if it can find that. If not, it will * default to the the zone "local".

    - * - * + * + * * @static * @return {string} the default time zone for ilib */ @@ -403,7 +403,7 @@ ilib.getTimeZone = function() { var ro = new Intl.DateTimeFormat().resolvedOptions(); ilib.tz = ro && ro.timeZone; } - + switch (ilib._getPlatform()) { case 'browser': // running in a browser @@ -429,8 +429,8 @@ ilib.getTimeZone = function() { } break; } - - ilib.tz = ilib.tz || "local"; + + ilib.tz = ilib.tz || "local"; } return ilib.tz; @@ -447,23 +447,23 @@ ilib.Loader = function() {}; /** * Load a set of files from where-ever it is stored.

    - * - * This is the main function define a callback function for loading missing locale + * + * This is the main function define a callback function for loading missing locale * data or resources. * If this copy of ilib is assembled without including the required locale data - * or resources, then that data can be lazy loaded dynamically when it is + * or resources, then that data can be lazy loaded dynamically when it is * needed by calling this method. Each ilib class will first - * check for the existence of data under ilib.data, and if it is not there, + * check for the existence of data under ilib.data, and if it is not there, * it will attempt to load it by calling this method of the laoder, and then place * it there.

    - * - * Suggested implementations of this method might load files - * directly from disk under nodejs or rhino, or within web pages, to load + * + * Suggested implementations of this method might load files + * directly from disk under nodejs or rhino, or within web pages, to load * files from the server with XHR calls.

    - * - * The first parameter to this method, paths, is an array of relative paths within - * the ilib dir structure for the - * requested data. These paths will already have the locale spec integrated + * + * The first parameter to this method, paths, is an array of relative paths within + * the ilib dir structure for the + * requested data. These paths will already have the locale spec integrated * into them, so no further tweaking needs to happen to load the data. Simply * load the named files. The second * parameter tells the loader whether to load the files synchronously or asynchronously. @@ -471,28 +471,28 @@ ilib.Loader = function() {}; * The third parameter gives extra parameters to the loader passed from the calling * code. This may contain any property/value pairs. The last parameter, callback, * is a callback function to call when all of the data is finishing loading. Make - * sure to call the callback with the context of "this" so that the caller has their + * sure to call the callback with the context of "this" so that the caller has their * context back again.

    - * - * The loader function must be able to operate either synchronously or asychronously. + * + * The loader function must be able to operate either synchronously or asychronously. * If the loader function is called with an undefined callback function, it is * expected to load the data synchronously, convert it to javascript - * objects, and return the array of json objects as the return value of the - * function. If the loader - * function is called with a callback function, it may load the data + * objects, and return the array of json objects as the return value of the + * function. If the loader + * function is called with a callback function, it may load the data * synchronously or asynchronously (doesn't matter which) as long as it calls * the callback function with the data converted to a javascript objects - * when it becomes available. If a particular file could not be loaded, the + * when it becomes available. If a particular file could not be loaded, the * loader function should put undefined into the corresponding entry in the - * results array. + * results array. * Note that it is important that all the data is loaded before the callback * is called.

    - * + * * An example implementation for nodejs might be: - * + * *

      * var fs = require("fs");
    - * 
    + *
      * var myLoader = function() {};
      * myLoader.prototype = new Loader();
      * myLoader.prototype.constructor = myLoader;
    @@ -504,7 +504,7 @@ ilib.Loader = function() {};
      *            var json = fs.readFileSync(path, "utf-8");
      *            ret.push(json ? JSON.parse(json) : undefined);
      *        });
    - *        
    + *
      *        return ret;
      *    }
      *    this.callback = callback;
    @@ -528,18 +528,18 @@ ilib.Loader = function() {};
      *        });
      *     }
      * }
    - * 
    + *
      * // bind to "this" so that "this" is relative to your own instance
      * ilib.setLoaderCallback(new myLoader());
      * 
    - * @param {Array.} paths An array of paths to load from wherever the files are stored + * @param {Array.} paths An array of paths to load from wherever the files are stored * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously - * @param {Object} params an object with any extra parameters for the loader. These can be + * @param {Object} params an object with any extra parameters for the loader. These can be * anything. The caller of the ilib class passes these parameters in. Presumably, the code that - * calls ilib and the code that provides the loader are together and can have a private + * calls ilib and the code that provides the loader are together and can have a private * agreement between them about what the parameters should contain. - * @param {function(Object)} callback function to call when the files are all loaded. The + * @param {function(Object)} callback function to call when the files are all loaded. The * parameter of the callback function is the contents of the files. */ ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; @@ -550,7 +550,7 @@ ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; * directories where files are loaded from and the values are an array * of strings containing the relative paths under the directory of each * file that can be loaded.

    - * + * * Example: *

      *  {
    @@ -566,7 +566,7 @@ ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {};
      *  }
      *  
    * @returns {Object} a hash containing directory names and - * paths to file that can be loaded by this loader + * paths to file that can be loaded by this loader */ ilib.Loader.prototype.listAvailableFiles = function() {}; @@ -582,10 +582,10 @@ ilib.Loader.prototype.listAvailableFiles = function() {}; ilib.Loader.prototype.isAvailable = function(path) {}; /** - * Set the custom loader used to load ilib's locale data in your environment. + * Set the custom loader used to load ilib's locale data in your environment. * The instance passed in must implement the Loader interface. See the - * Loader class documentation for more information about loaders. - * + * Loader class documentation for more information about loaders. + * * @static * @param {ilib.Loader} loader class to call to access the requested data. * @return {boolean} true if the loader was installed correctly, or false @@ -593,7 +593,7 @@ ilib.Loader.prototype.isAvailable = function(path) {}; */ ilib.setLoaderCallback = function(loader) { // only a basic check - if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || + if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || typeof(loader) === 'function' || typeof(loader) === 'undefined') { //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); ilib._load = loader; @@ -603,137 +603,137 @@ ilib.setLoaderCallback = function(loader) { }; /** - * Return the custom Loader instance currently in use with this instance + * Return the custom Loader instance currently in use with this instance * of ilib. If there is no loader, this method returns undefined. - * + * * @protected * @static - * @return {ilib.Loader|undefined} the loader instance currently in use, or + * @return {ilib.Loader|undefined} the loader instance currently in use, or * undefined if there is no such loader */ ilib.getLoader = function() { - return ilib._load; + return ilib._load; }; /** - * Test whether an object is an javascript array. - * + * Test whether an object is an javascript array. + * * @static * @param {*} object The object to test * @return {boolean} return true if the object is an array * and false otherwise */ ilib.isArray = function(object) { - if (typeof(object) === 'object') { - return Object.prototype.toString.call(object) === '[object Array]'; - } - return false; + if (typeof(object) === 'object') { + return Object.prototype.toString.call(object) === '[object Array]'; + } + return false; }; /** * Extend object1 by mixing in everything from object2 into it. The objects * are deeply extended, meaning that this method recursively descends the * tree in the objects and mixes them in at each level. Arrays are extended - * by concatenating the elements of object2 onto those of object1. - * + * by concatenating the elements of object2 onto those of object1. + * * @static * @param {Object} object1 the target object to extend * @param {Object=} object2 the object to mix in to object1 * @return {Object} returns object1 */ ilib.extend = function (object1, object2) { - var prop = undefined; - if (object2) { - for (prop in object2) { - // don't extend object with undefined or functions - if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { - if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { - //console.log("Merging array prop " + prop); - object1[prop] = object1[prop].concat(object2[prop]); - } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { - //console.log("Merging object prop " + prop); - if (prop !== "ilib") { - object1[prop] = ilib.extend(object1[prop], object2[prop]); - } - } else { - //console.log("Copying prop " + prop); - // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily - object1[prop] = object2[prop]; - } - } - } - } - return object1; + var prop = undefined; + if (object2) { + for (prop in object2) { + // don't extend object with undefined or functions + if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { + if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { + //console.log("Merging array prop " + prop); + object1[prop] = object1[prop].concat(object2[prop]); + } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { + //console.log("Merging object prop " + prop); + if (prop !== "ilib") { + object1[prop] = ilib.extend(object1[prop], object2[prop]); + } + } else { + //console.log("Copying prop " + prop); + // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily + object1[prop] = object2[prop]; + } + } + } + } + return object1; }; ilib.extend2 = function (object1, object2) { - var prop = undefined; - if (object2) { - for (prop in object2) { - // don't extend object with undefined or functions - if (prop && typeof(object2[prop]) !== 'undefined') { - if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { - //console.log("Merging array prop " + prop); - object1[prop] = object1[prop].concat(object2[prop]); - } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { - //console.log("Merging object prop " + prop); - if (prop !== "ilib") { - object1[prop] = ilib.extend2(object1[prop], object2[prop]); - } - } else { - //console.log("Copying prop " + prop); - // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily - object1[prop] = object2[prop]; - } - } - } - } - return object1; + var prop = undefined; + if (object2) { + for (prop in object2) { + // don't extend object with undefined or functions + if (prop && typeof(object2[prop]) !== 'undefined') { + if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { + //console.log("Merging array prop " + prop); + object1[prop] = object1[prop].concat(object2[prop]); + } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { + //console.log("Merging object prop " + prop); + if (prop !== "ilib") { + object1[prop] = ilib.extend2(object1[prop], object2[prop]); + } + } else { + //console.log("Copying prop " + prop); + // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily + object1[prop] = object2[prop]; + } + } + } + } + return object1; }; /** * If Function.prototype.bind does not exist in this JS engine, this * function reimplements it in terms of older JS functions. * bind() doesn't exist in many older browsers. - * + * * @static * @param {Object} scope object that the method should operate on * @param {function(...)} method method to call - * @return {function(...)|undefined} function that calls the given method + * @return {function(...)|undefined} function that calls the given method * in the given scope with all of its arguments properly attached, or * undefined if there was a problem with the arguments */ ilib.bind = function(scope, method/*, bound arguments*/){ - if (!scope || !method) { - return undefined; - } - - /** @protected - * @param {Arguments} inArrayLike - * @param {number=} inOffset - */ - function cloneArray(inArrayLike, inOffset) { - var arr = []; - for(var i = inOffset || 0, l = inArrayLike.length; i - * + * * @static * @param {string|IString|number} ch character or code point to examine * @return {boolean} true if the first character is alphabetic or numeric */ var isAlnum = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return isAlpha(num) || isDigit(num); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return isAlpha(num) || isDigit(num); }; /** @@ -53,9 +53,9 @@ var isAlnum = function (ch) { * @param {function(*)|undefined} onLoad */ isAlnum._init = function (sync, loadParams, onLoad) { - isAlpha._init(sync, loadParams, function () { - isDigit._init(sync, loadParams, onLoad); - }); + isAlpha._init(sync, loadParams, function () { + isDigit._init(sync, loadParams, onLoad); + }); }; module.exports = isAlnum; diff --git a/js/lib/isAlpha.js b/js/lib/isAlpha.js index 85c91ac3f3..afab9e4b83 100644 --- a/js/lib/isAlpha.js +++ b/js/lib/isAlpha.js @@ -19,7 +19,7 @@ // !data ctype_l -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -31,27 +31,27 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is alphabetic. */ var isAlpha = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return ilib.data.ctype_l ? - (CType._inRange(num, 'Lu', ilib.data.ctype_l) || - CType._inRange(num, 'Ll', ilib.data.ctype_l) || - CType._inRange(num, 'Lt', ilib.data.ctype_l) || - CType._inRange(num, 'Lm', ilib.data.ctype_l) || - CType._inRange(num, 'Lo', ilib.data.ctype_l)) : - ((num >= 0x41 && num <= 0x5A) || (num >= 0x61 && num <= 0x7A)); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return ilib.data.ctype_l ? + (CType._inRange(num, 'Lu', ilib.data.ctype_l) || + CType._inRange(num, 'Ll', ilib.data.ctype_l) || + CType._inRange(num, 'Lt', ilib.data.ctype_l) || + CType._inRange(num, 'Lm', ilib.data.ctype_l) || + CType._inRange(num, 'Lo', ilib.data.ctype_l)) : + ((num >= 0x41 && num <= 0x5A) || (num >= 0x61 && num <= 0x7A)); }; /** @@ -61,7 +61,7 @@ var isAlpha = function (ch) { * @param {function(*)|undefined} onLoad */ isAlpha._init = function (sync, loadParams, onLoad) { - CType._load("ctype_l", sync, loadParams, onLoad); + CType._load("ctype_l", sync, loadParams, onLoad); }; module.exports = isAlpha; \ No newline at end of file diff --git a/js/lib/isAscii.js b/js/lib/isAscii.js index 9e62cadab8..5f650a56d6 100644 --- a/js/lib/isAscii.js +++ b/js/lib/isAscii.js @@ -19,7 +19,7 @@ // !data ctype -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -31,21 +31,21 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is in the ASCII range. */ var isAscii = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return ilib.data.ctype ? CType._inRange(num, 'ascii', ilib.data.ctype) : (num <= 0x7F); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return ilib.data.ctype ? CType._inRange(num, 'ascii', ilib.data.ctype) : (num <= 0x7F); }; /** @@ -55,7 +55,7 @@ var isAscii = function (ch) { * @param {function(*)|undefined} onLoad */ isAscii._init = function (sync, loadParams, onLoad) { - CType._init(sync, loadParams, onLoad); + CType._init(sync, loadParams, onLoad); }; module.exports = isAscii; \ No newline at end of file diff --git a/js/lib/isBlank.js b/js/lib/isBlank.js index dd79ab7bde..346fec401d 100644 --- a/js/lib/isBlank.js +++ b/js/lib/isBlank.js @@ -19,7 +19,7 @@ // !data ctype -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -32,21 +32,21 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is a blank character. */ var isBlank = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return ilib.data.ctype ? CType._inRange(num, 'blank', ilib.data.ctype) : (ch === ' ' || ch === '\t'); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return ilib.data.ctype ? CType._inRange(num, 'blank', ilib.data.ctype) : (ch === ' ' || ch === '\t'); }; /** @@ -56,7 +56,7 @@ var isBlank = function (ch) { * @param {function(*)|undefined} onLoad */ isBlank._init = function (sync, loadParams, onLoad) { - CType._init(sync, loadParams, onLoad); + CType._init(sync, loadParams, onLoad); }; module.exports = isBlank; \ No newline at end of file diff --git a/js/lib/isCntrl.js b/js/lib/isCntrl.js index e990745663..49fb2c4cfd 100644 --- a/js/lib/isCntrl.js +++ b/js/lib/isCntrl.js @@ -1,6 +1,6 @@ /* * isCntrl.js - Character type is control character - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,33 +19,33 @@ // !data ctype_c -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); /** * Return whether or not the first character is a control character.

    - * + * * @static * @param {string|IString|number} ch character or code point to examine * @return {boolean} true if the first character is a control character. */ var isCntrl = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return CType._inRange(num, 'Cc', ilib.data.ctype_c); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return CType._inRange(num, 'Cc', ilib.data.ctype_c); }; /** @@ -55,7 +55,7 @@ var isCntrl = function (ch) { * @param {function(*)|undefined} onLoad */ isCntrl._init = function (sync, loadParams, onLoad) { - CType._load("ctype_c", sync, loadParams, onLoad); + CType._load("ctype_c", sync, loadParams, onLoad); }; module.exports = isCntrl; \ No newline at end of file diff --git a/js/lib/isDigit.js b/js/lib/isDigit.js index 2db7c794dd..1426f3fd76 100644 --- a/js/lib/isDigit.js +++ b/js/lib/isDigit.js @@ -19,7 +19,7 @@ // !data ctype ctype_n -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -33,21 +33,21 @@ var IString = require("./IString.js"); * Latin script. */ var isDigit = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return ilib.data.ctype ? CType._inRange(num, 'Nd', ilib.data.ctype_n) : (num >= 0x30 && num <= 0x39); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return ilib.data.ctype ? CType._inRange(num, 'Nd', ilib.data.ctype_n) : (num >= 0x30 && num <= 0x39); }; /** diff --git a/js/lib/isGraph.js b/js/lib/isGraph.js index 9bb18b165b..fa20743324 100644 --- a/js/lib/isGraph.js +++ b/js/lib/isGraph.js @@ -1,6 +1,6 @@ /* * isGraph.js - Character type is graph char - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,28 +24,28 @@ var isCntrl = require("./isCntrl.js"); /** * Return whether or not the first character is any printable character * other than space.

    - * + * * @static * @param {string|IString|number} ch character or code point to examine * @return {boolean} true if the first character is any printable character - * other than space. + * other than space. */ var isGraph = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } - return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } + return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); }; /** @@ -55,9 +55,9 @@ var isGraph = function (ch) { * @param {function(*)|undefined} onLoad */ isGraph._init = function (sync, loadParams, onLoad) { - isSpace._init(sync, loadParams, function () { - isCntrl._init(sync, loadParams, onLoad); - }); + isSpace._init(sync, loadParams, function () { + isCntrl._init(sync, loadParams, onLoad); + }); }; module.exports = isGraph; diff --git a/js/lib/isIdeo.js b/js/lib/isIdeo.js index 834031e737..db61dfd2c5 100644 --- a/js/lib/isIdeo.js +++ b/js/lib/isIdeo.js @@ -1,6 +1,6 @@ /* * isIdeo.js - Character type definitions - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,38 +19,38 @@ // !data ctype -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); /** * Return whether or not the first character is an ideographic character.

    - * + * * @static * @param {string|IString|number} ch character or code point to examine * @return {boolean} true if the first character is an ideographic character. */ var isIdeo = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return CType._inRange(num, 'cjk', ilib.data.ctype) || - CType._inRange(num, 'cjkradicals', ilib.data.ctype) || - CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || - CType._inRange(num, 'cjkpunct', ilib.data.ctype) || - CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); + return CType._inRange(num, 'cjk', ilib.data.ctype) || + CType._inRange(num, 'cjkradicals', ilib.data.ctype) || + CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || + CType._inRange(num, 'cjkpunct', ilib.data.ctype) || + CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); }; /** @@ -60,7 +60,7 @@ var isIdeo = function (ch) { * @param {function(*)|undefined} onLoad */ isIdeo._init = function (sync, loadParams, onLoad) { - CType._init(sync, loadParams, onLoad); + CType._init(sync, loadParams, onLoad); }; module.exports = isIdeo; \ No newline at end of file diff --git a/js/lib/isLower.js b/js/lib/isLower.js index 06dce77c79..b268fc40aa 100644 --- a/js/lib/isLower.js +++ b/js/lib/isLower.js @@ -19,7 +19,7 @@ // !data ctype_l -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -34,22 +34,22 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is lower-case. */ var isLower = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return ilib.data.ctype_l ? CType._inRange(num, 'Ll', ilib.data.ctype_l) : (num >= 0x61 && num <= 0x7A); + return ilib.data.ctype_l ? CType._inRange(num, 'Ll', ilib.data.ctype_l) : (num >= 0x61 && num <= 0x7A); }; /** @@ -59,7 +59,7 @@ var isLower = function (ch) { * @param {function(*)|undefined} onLoad */ isLower._init = function (sync, loadParams, onLoad) { - CType._load("ctype_l", sync, loadParams, onLoad); + CType._load("ctype_l", sync, loadParams, onLoad); }; module.exports = isLower; \ No newline at end of file diff --git a/js/lib/isPrint.js b/js/lib/isPrint.js index 6403968d0e..8b86a157df 100644 --- a/js/lib/isPrint.js +++ b/js/lib/isPrint.js @@ -1,6 +1,6 @@ /* * isPrint.js - Character type is printable char - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +22,13 @@ var isCntrl = require("./isCntrl.js"); /** * Return whether or not the first character is any printable character, * including space.

    - * + * * @static * @param {string|IString|number} ch character or code point to examine * @return {boolean} true if the first character is printable. */ var isPrint = function (ch) { - return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); + return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); }; /** @@ -38,7 +38,7 @@ var isPrint = function (ch) { * @param {function(*)|undefined} onLoad */ isPrint._init = function (sync, loadParams, onLoad) { - isCntrl._init(sync, loadParams, onLoad); + isCntrl._init(sync, loadParams, onLoad); }; module.exports = isPrint; diff --git a/js/lib/isPunct.js b/js/lib/isPunct.js index 8eee9c929d..f220e0bb56 100644 --- a/js/lib/isPunct.js +++ b/js/lib/isPunct.js @@ -19,7 +19,7 @@ // !data ctype_p -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -32,33 +32,33 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is punctuation. */ var isPunct = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return ilib.data.ctype_p ? - (CType._inRange(num, 'Pd', ilib.data.ctype_p) || - CType._inRange(num, 'Ps', ilib.data.ctype_p) || - CType._inRange(num, 'Pe', ilib.data.ctype_p) || - CType._inRange(num, 'Pc', ilib.data.ctype_p) || - CType._inRange(num, 'Po', ilib.data.ctype_p) || - CType._inRange(num, 'Pi', ilib.data.ctype_p) || - CType._inRange(num, 'Pf', ilib.data.ctype_p)) : - ((num >= 0x21 && num <= 0x2F) || - (num >= 0x3A && num <= 0x40) || - (num >= 0x5B && num <= 0x60) || - (num >= 0x7B && num <= 0x7E)); + return ilib.data.ctype_p ? + (CType._inRange(num, 'Pd', ilib.data.ctype_p) || + CType._inRange(num, 'Ps', ilib.data.ctype_p) || + CType._inRange(num, 'Pe', ilib.data.ctype_p) || + CType._inRange(num, 'Pc', ilib.data.ctype_p) || + CType._inRange(num, 'Po', ilib.data.ctype_p) || + CType._inRange(num, 'Pi', ilib.data.ctype_p) || + CType._inRange(num, 'Pf', ilib.data.ctype_p)) : + ((num >= 0x21 && num <= 0x2F) || + (num >= 0x3A && num <= 0x40) || + (num >= 0x5B && num <= 0x60) || + (num >= 0x7B && num <= 0x7E)); }; /** @@ -68,7 +68,7 @@ var isPunct = function (ch) { * @param {function(*)|undefined} onLoad */ isPunct._init = function (sync, loadParams, onLoad) { - CType._load("ctype_p", sync, loadParams, onLoad); + CType._load("ctype_p", sync, loadParams, onLoad); }; module.exports = isPunct; diff --git a/js/lib/isScript.js b/js/lib/isScript.js index 56b8beaf2c..4e1ce34724 100644 --- a/js/lib/isScript.js +++ b/js/lib/isScript.js @@ -1,6 +1,6 @@ /* * isScript.js - Character type is script - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +19,16 @@ // !data scriptToRange -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); /** - * Return whether or not the first character in the given string is + * Return whether or not the first character in the given string is * in the given script. The script is given as the 4-letter ISO * 15924 script code.

    - * + * * @static * @param {string|IString|number} ch character or code point to examine * @param {string} script the 4-letter ISO 15924 to query against @@ -36,22 +36,22 @@ var IString = require("./IString.js"); * false otherwise */ var isScript = function (ch, script) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return CType._inRange(num, script, ilib.data.scriptToRange); + return CType._inRange(num, script, ilib.data.scriptToRange); }; /** @@ -61,7 +61,7 @@ var isScript = function (ch, script) { * @param {function(*)|undefined} onLoad */ isScript._init = function (sync, loadParams, onLoad) { - CType._load("scriptToRange", sync, loadParams, onLoad); + CType._load("scriptToRange", sync, loadParams, onLoad); }; module.exports = isScript; \ No newline at end of file diff --git a/js/lib/isSpace.js b/js/lib/isSpace.js index 807cb39e25..fd38201840 100644 --- a/js/lib/isSpace.js +++ b/js/lib/isSpace.js @@ -19,7 +19,7 @@ // !data ctype ctype_z -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -32,28 +32,28 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is a whitespace character. */ var isSpace = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return ilib.data.ctype && ilib.data.ctype_z ? - (CType._inRange(num, 'space', ilib.data.ctype) || - CType._inRange(num, 'Zs', ilib.data.ctype_z) || - CType._inRange(num, 'Zl', ilib.data.ctype_z) || - CType._inRange(num, 'Zp', ilib.data.ctype_z)) : - (ch === ' ' || num === 0xA0 || - (num >= 0x09 && num <= 0x0D)); + return ilib.data.ctype && ilib.data.ctype_z ? + (CType._inRange(num, 'space', ilib.data.ctype) || + CType._inRange(num, 'Zs', ilib.data.ctype_z) || + CType._inRange(num, 'Zl', ilib.data.ctype_z) || + CType._inRange(num, 'Zp', ilib.data.ctype_z)) : + (ch === ' ' || num === 0xA0 || + (num >= 0x09 && num <= 0x0D)); }; /** @@ -63,9 +63,9 @@ var isSpace = function (ch) { * @param {function(*)|undefined} onLoad */ isSpace._init = function (sync, loadParams, onLoad) { - CType._load("ctype_z", sync, loadParams, function () { - CType._init(sync, loadParams, onLoad); - }); + CType._load("ctype_z", sync, loadParams, function () { + CType._init(sync, loadParams, onLoad); + }); }; module.exports = isSpace; \ No newline at end of file diff --git a/js/lib/isUpper.js b/js/lib/isUpper.js index 41be4c06df..72f21b4343 100644 --- a/js/lib/isUpper.js +++ b/js/lib/isUpper.js @@ -19,7 +19,7 @@ // !data ctype_l -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -34,22 +34,22 @@ var IString = require("./IString.js"); * @return {boolean} true if the first character is upper-case. */ var isUpper = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return ilib.data.ctype_l ? CType._inRange(num, 'Lu', ilib.data.ctype_l) : (num >= 0x41 && num <= 0x5A); + return ilib.data.ctype_l ? CType._inRange(num, 'Lu', ilib.data.ctype_l) : (num >= 0x41 && num <= 0x5A); }; /** @@ -59,7 +59,7 @@ var isUpper = function (ch) { * @param {function(*)|undefined} onLoad */ isUpper._init = function (sync, loadParams, onLoad) { - CType._load("ctype_l", sync, loadParams, onLoad); + CType._load("ctype_l", sync, loadParams, onLoad); }; module.exports = isUpper; \ No newline at end of file diff --git a/js/lib/isXdigit.js b/js/lib/isXdigit.js index d771d13741..0c803ea2f0 100644 --- a/js/lib/isXdigit.js +++ b/js/lib/isXdigit.js @@ -19,7 +19,7 @@ // !data ctype -var ilib = require("./ilib.js"); +var ilib = require("../index"); var CType = require("./CType.js"); var IString = require("./IString.js"); @@ -34,23 +34,23 @@ var IString = require("./IString.js"); * in the Latin script. */ var isXdigit = function (ch) { - var num; - switch (typeof(ch)) { - case 'number': - num = ch; - break; - case 'string': - num = IString.toCodePoint(ch, 0); - break; - case 'undefined': - return false; - default: - num = ch._toCodePoint(0); - break; - } + var num; + switch (typeof(ch)) { + case 'number': + num = ch; + break; + case 'string': + num = IString.toCodePoint(ch, 0); + break; + case 'undefined': + return false; + default: + num = ch._toCodePoint(0); + break; + } - return ilib.data.ctype ? CType._inRange(num, 'xdigit', ilib.data.ctype) : - ((num >= 0x30 && num <= 0x39) || (num >= 0x41 && num <= 0x46) || (num >= 0x61 && num <= 0x66)); + return ilib.data.ctype ? CType._inRange(num, 'xdigit', ilib.data.ctype) : + ((num >= 0x30 && num <= 0x39) || (num >= 0x41 && num <= 0x46) || (num >= 0x61 && num <= 0x66)); }; /** @@ -60,7 +60,7 @@ var isXdigit = function (ch) { * @param {function(*)|undefined} onLoad */ isXdigit._init = function (sync, loadParams, onLoad) { - CType._init(sync, loadParams, onLoad); + CType._init(sync, loadParams, onLoad); }; module.exports = isXdigit; \ No newline at end of file diff --git a/js/lib/metafiles/ilib-core-webpack.js b/js/lib/metafiles/ilib-core-webpack.js index 737de33a0c..0e14fabed7 100644 --- a/js/lib/metafiles/ilib-core-webpack.js +++ b/js/lib/metafiles/ilib-core-webpack.js @@ -1,6 +1,6 @@ /** * ilib-core-webpack.js - metafile that includes a minimal set of other js files - * + * * @license * Copyright © 2018, JEDLSoft * @@ -28,7 +28,4 @@ ilib.ScriptInfo = require("../ScriptInfo.js"); //This unpacks the above classes to the global scope require("../ilib-unpack.js"); -// Must be at the end of meta file -require("../ilib-getdata.js"); - module.exports = ilib; \ No newline at end of file diff --git a/js/lib/metafiles/ilib-demo-webpack.js b/js/lib/metafiles/ilib-demo-webpack.js index 40da19cc1a..d19143191a 100644 --- a/js/lib/metafiles/ilib-demo-webpack.js +++ b/js/lib/metafiles/ilib-demo-webpack.js @@ -1,7 +1,7 @@ /** - * ilib-demo-webpack.js - metafile that includes all of the other js files + * ilib-demo-webpack.js - metafile that includes all of the other js files * for the demo site - * + * * @license * Copyright © 2018, JEDLSoft * @@ -116,7 +116,4 @@ ilib.DigitalSpeedUnit = require("../DigitalSpeedUnit.js"); // This unpacks the above classes to the global scope require("../ilib-unpack.js"); -// Must be at the end of meta file -require("../ilib-getdata.js"); - module.exports = ilib; \ No newline at end of file diff --git a/js/lib/metafiles/ilib-full-webpack.js b/js/lib/metafiles/ilib-full-webpack.js index 2179153c70..391897c65c 100644 --- a/js/lib/metafiles/ilib-full-webpack.js +++ b/js/lib/metafiles/ilib-full-webpack.js @@ -1,6 +1,6 @@ /** * ilib-full-webpack.js - metafile that includes all of the other js files - * + * * @license * Copyright © 2018, JEDLSoft * @@ -116,7 +116,4 @@ ilib.DigitalSpeedUnit = require("../DigitalSpeedUnit.js"); //This unpacks the above classes to the global scope require("../ilib-unpack.js"); -// Must be at the end of meta file -require("../ilib-getdata.js"); - module.exports = ilib; \ No newline at end of file diff --git a/js/lib/metafiles/ilib-standard-webpack.js b/js/lib/metafiles/ilib-standard-webpack.js index 1a366fd1eb..b2bb074274 100644 --- a/js/lib/metafiles/ilib-standard-webpack.js +++ b/js/lib/metafiles/ilib-standard-webpack.js @@ -1,6 +1,6 @@ /** * ilib-standard-webpack.js - metafile that includes a reasonable set of other js files - * + * * @license * Copyright © 2018, JEDLSoft * @@ -46,7 +46,4 @@ ilib.TimeZone = require("../TimeZone.js"); //This unpacks the above classes to the global scope require("../ilib-unpack.js"); -// Must be at the end of meta file -require("../ilib-getdata.js"); - module.exports = ilib; \ No newline at end of file diff --git a/js/lib/metafiles/ilib-ut-webpack.js b/js/lib/metafiles/ilib-ut-webpack.js index 09ea71644f..201f449d3a 100644 --- a/js/lib/metafiles/ilib-ut-webpack.js +++ b/js/lib/metafiles/ilib-ut-webpack.js @@ -1,7 +1,7 @@ /** * ilib-ut-webpack.js - metafile that includes all of the other js files that * the unit tests need - * + * * @license * Copyright © 2018, JEDLSoft * @@ -150,7 +150,4 @@ ilib.RataDie = require("../RataDie.js"); // This unpacks the above classes to the global scope require("../ilib-unpack.js"); -// Must be at the end of meta file -require("../ilib-getdata.js"); - module.exports = ilib; \ No newline at end of file diff --git a/js/package.json.template b/js/package.json.template index af730ecbca..960dab747c 100644 --- a/js/package.json.template +++ b/js/package.json.template @@ -1,7 +1,7 @@ { "name": "ilib", "version": "@fullversion@", - "main": "lib/ilib-node.js", + "main": "index.js", "description": "iLib is a cross-engine library of internationalization (i18n) classes written in pure JS", "keywords": [ "internationalization", @@ -48,7 +48,8 @@ "files": [ "lib", "locale", - "README.md" + "README.md", + "index.js" ], "repository": { "type": "git", diff --git a/js/test/root/testglobal.js b/js/test/root/testglobal.js index 475be449a1..7391b4fe57 100644 --- a/js/test/root/testglobal.js +++ b/js/test/root/testglobal.js @@ -70,7 +70,7 @@ module.exports.testglobal = { testGetVersion: function(test) { test.expect(1); - test.equal(ilib.getVersion().substring(0,4), "14.0"); + test.equal(ilib.getVersion().substring(0,4), "14.1"); test.done(); }, diff --git a/js/webpack.config.js b/js/webpack.config.js index 8388bcf8d1..268b1f3362 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -43,7 +43,7 @@ module.exports = function(env, args) { if (compilationType !== "compiled" && compilationType !== "uncompiled") { compilationType = "uncompiled"; } - + if (target !== "web" && target !== "node") { // TODO add other targets here as necessary target = "web"; @@ -112,7 +112,7 @@ module.exports = function(env, args) { if (target !== "web") { ret.target = target; } - + ret.output.filename = "ilib-" + size; if (assembly === "dynamicdata") { ret.output.filename += "-dyn"; diff --git a/package.json b/package.json index 57850155cd..e8a369b783 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ilib", - "version": "14.0.0", + "version": "14.1.0", "main": "js/lib/ilib-node.js", "description": "iLib is a cross-engine library of internationalization (i18n) classes written in pure JS", "keywords": [ @@ -89,5 +89,8 @@ "test": "ant test", "build.web": "webpack -p", "watch": "webpack --watch" + }, + "publishConfig": { + "tag": "experimental" } } diff --git a/tools/locmaker/locmaker.js b/tools/locmaker/locmaker.js index 333b8739d1..a580c91ff4 100644 --- a/tools/locmaker/locmaker.js +++ b/tools/locmaker/locmaker.js @@ -28,7 +28,7 @@ var util = require('util'); var path = require('path'); // var clargs = require('command-line-args'); -var ilib = require("ilib"); +var ilib = require("../index"); var Locale = require("ilib/lib/Locale"); var DateFmt = require("ilib/lib/DateFmt");