- * - * 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:
- * + * *
- * + * * The options parameter may contain any of the following properties: - * + * *
- * + * * 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"); * *
* - * 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 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: - * + * * *
- * + * * @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 - * + * *- 0 March equinox *
- 1 June solstice *
- 2 September equinox *
- 3 December solstice *
*
- * + * * @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: - * + * *- Cn - Unassigned *
- Lu - Uppercase_Letter @@ -71,7 +71,7 @@ var CType = {}; *
- Pi - Initial_Punctuation *
- Pf - Final_Punctuation *
*
- 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: - * + * *
*
- * + * * 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.- 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. *
} 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: - * + * *
- *
- * - * + * + * * @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.- 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". *
- * + * * 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: - * + * *
*
- * - * 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); // HHHH; - 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); // HHHH; + 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 = "" + 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 = "" + 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.- 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: *
*
* The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *- 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 *
- 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: *
*
* The default if this style is not specified is "js" for Javascript. *- 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"); *
|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: - * + * *
- *
- * - * 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.- 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: *
*
* The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *- 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 *
- 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: *
*
* The default if this style is not specified is "js" for Javascript. - * - *- 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}" *
- 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. *
- * + * * 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: - * + * *
- *
- * - * 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.- 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: *
*
* The default value for the missing property if not otherwise specified is "escape" * so that information is not lost. - * - *- 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 *
- 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: *
*
* The default if this style is not specified is "js" for Javascript. - * - *- 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}" *
- 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. *
|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: - * + * *
*
- * - * 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.- 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. *
} 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" *
*
- * + * *- 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 *
- 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. *
- 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. *
- 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:
- * + * *
*
- * - * 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:* ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"] **- * + * *
*
** 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: - * + * *- *
- * + * * 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.- 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 + *
- 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. - *
- A few scripts - if you are sorting text written in only a few scripts, you may + *
- 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. *
- 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. *
- * + * * 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: - * + * *
- *
- * + * * If called with another Coptic date argument, the date components of the given * date are copied into the current one.- 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 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: - * + * *
- *
* * 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.- 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 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: - * + * *
*
- * - * 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.- 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. *
- * - * 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: - * + * *
*
- * - * 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.- 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. *
- * + * * 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: - * + * *
*
- * - * 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.- 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. - * + * *
*
- * + * * 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.- 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 *
- * + * * 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: - * + * *
*
* 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.- 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 *
- * - * 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: - * + * *
*
- * + * * 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.- 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 *
- * + * * 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. *
- * + * * 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: - * + * *
- *
- * + * * All other options will be ignored and their corresponding getter methods will * return the empty string.- 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. *
- * - * + * + * * @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 = { *
*
@@ -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.- 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. *
, - * 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: - * - *
- *
- * - * @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"}) - *- 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. - *
- * The options parameter may contain any of the following properties: - * - *
- *
- * @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.- length - length of the names of the months being sought. This may be one of - * "short", "medium", "long", or "full" - *
} 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:
- * - *
- *
- * - * @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.- 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" - *
, + * 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: + * + *
+ *
+ * + * @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"}) + *- 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. + *
+ * The options parameter may contain any of the following properties: + * + *
+ *
+ * @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.- length - length of the names of the months being sought. This may be one of + * "short", "medium", "long", or "full" + *
} 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:
+ * + *
+ *
+ * + * @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.- 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" + *
- * + * * 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. - * + * *
*
- * - * 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.- 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. *
- * + * * 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: - * - *
- *
- * - * 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".- 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 - *
- * - * 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: + * + *
+ *
+ * + * 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".- 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 + *
+ * + * 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.- year *
- month @@ -339,82 +339,82 @@ DurationFmt.prototype._init = function(locinfo, options) { * * When a property is left out of the components parameter or has a value of 0, it will not * be formatted into the output string, except for times that include 0 minutes and 0 seconds. - * + * * This formatter will not ensure that numbers for each component property is within the * valid range for that component. This allows you to format durations that are longer * than normal range. For example, you could format a duration has being "33 hours" rather * than "1 day, 9 hours". - * + * * @param {Object} components date/time components to be formatted into a duration string - * @return {IString} a string with the duration formatted according to the style and - * locale set up for this formatter instance. If the components parameter is empty or + * @return {IString} a string with the duration formatted according to the style and + * locale set up for this formatter instance. If the components parameter is empty or * undefined, an empty string is returned. */ DurationFmt.prototype.format = function (components) { - var i, list, fmt, secondlast = true, str = ""; - - list = DurationFmt.complist[this.style]; - //for (i = 0; i < list.length; i++) { - for (i = list.length-1; i >= 0; i--) { - //console.log("Now dealing with " + list[i]); - if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { - if (str.length > 0) { - str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; - secondlast = false; - } - str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; - } - } + var i, list, fmt, secondlast = true, str = ""; + + list = DurationFmt.complist[this.style]; + //for (i = 0; i < list.length; i++) { + for (i = list.length-1; i >= 0; i--) { + //console.log("Now dealing with " + list[i]); + if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { + if (str.length > 0) { + str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; + secondlast = false; + } + str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; + } + } + + if (this.style === 'clock') { + if (typeof(components.hour) !== 'undefined') { + fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; + } else { + fmt = this.timeFmtMS; + } - if (this.style === 'clock') { - if (typeof(components.hour) !== 'undefined') { - fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; - } else { - fmt = this.timeFmtMS; - } - - if (str.length > 0) { - str += this.components.separator; - } - str += fmt._formatTemplate(components, fmt.templateArr); - } + if (str.length > 0) { + str += this.components.separator; + } + str += fmt._formatTemplate(components, fmt.templateArr); + } - if (this.scriptDirection === 'rtl') { - str = "\u200F" + str; - } - return new IString(str); + if (this.scriptDirection === 'rtl') { + str = "\u200F" + str; + } + return new IString(str); }; /** * Return the locale that was used to construct this duration formatter object. If the * locale was not given as parameter to the constructor, this method returns the default * locale of the system. - * + * * @return {Locale} locale that this duration formatter was constructed with */ DurationFmt.prototype.getLocale = function () { - return this.locale; + return this.locale; }; /** * Return the length that was used to construct this duration formatter object. If the * length was not given as parameter to the constructor, this method returns the default * length. Valid values are "short", "medium", "long", and "full". - * + * * @return {string} length that this duration formatter was constructed with */ DurationFmt.prototype.getLength = function () { - return this.length; + return this.length; }; /** * Return the style that was used to construct this duration formatter object. Returns * one of "text" or "clock". - * + * * @return {string} style that this duration formatter was constructed with */ DurationFmt.prototype.getStyle = function () { - return this.style; + return this.style; }; module.exports = DurationFmt; diff --git a/js/lib/ElementIterator.js b/js/lib/ElementIterator.js index f22ae5faf2..d73d9861eb 100644 --- a/js/lib/ElementIterator.js +++ b/js/lib/ElementIterator.js @@ -1,6 +1,6 @@ /* * ElementIterator.js - Iterate through a list of collation elements - * + * * Copyright © 2013-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,50 +23,50 @@ * iterator takes a source of code points, converts them into * collation elements, and allows the caller to get single * elements at a time. - * + * * @constructor * @private - * @param {CodePointSource} source source of code points to + * @param {CodePointSource} source source of code points to * convert to collation elements * @param {Object} map mapping from sequences of code points to * collation elements * @param {number} keysize size in bits of the collation elements */ var ElementIterator = function (source, map, keysize) { - this.elements = []; - this.source = source; - this.map = map; - this.keysize = keysize; + this.elements = []; + this.source = source; + this.map = map; + this.keysize = keysize; }; /** * @private */ ElementIterator.prototype._fillBuffer = function () { - var str = undefined; - - // peek ahead by up to 4 characters, which may combine - // into 1 or more collation elements - for (var i = 4; i > 0; i--) { - str = this.source.peek(i); - if (str && this.map[str]) { - this.elements = this.elements.concat(this.map[str]); - this.source.consume(i); - return; - } - } - - if (str) { - // no mappings for the first code point, so just use its - // Unicode code point as a proxy for its sort order. Shift - // it by the key size so that everything unknown sorts - // after things that have mappings - this.elements.push(str.charCodeAt(0) << this.keysize); - this.source.consume(1); - } else { - // end of the string - return undefined; - } + var str = undefined; + + // peek ahead by up to 4 characters, which may combine + // into 1 or more collation elements + for (var i = 4; i > 0; i--) { + str = this.source.peek(i); + if (str && this.map[str]) { + this.elements = this.elements.concat(this.map[str]); + this.source.consume(i); + return; + } + } + + if (str) { + // no mappings for the first code point, so just use its + // Unicode code point as a proxy for its sort order. Shift + // it by the key size so that everything unknown sorts + // after things that have mappings + this.elements.push(str.charCodeAt(0) << this.keysize); + this.source.consume(1); + } else { + // end of the string + return undefined; + } }; /** @@ -76,29 +76,29 @@ ElementIterator.prototype._fillBuffer = function () { * iterate through, and false otherwise */ ElementIterator.prototype.hasNext = function () { - if (this.elements.length < 1) { - this._fillBuffer(); - } - return !!this.elements.length; + if (this.elements.length < 1) { + this._fillBuffer(); + } + return !!this.elements.length; }; /** - * Return the next collation element. If more than one collation - * element is generated from a sequence of code points + * Return the next collation element. If more than one collation + * element is generated from a sequence of code points * (ie. an "expansion"), then this class will buffer the - * other elements and return them on subsequent calls to + * other elements and return them on subsequent calls to * this method. - * + * * @returns {number|undefined} the next collation element or * undefined for no more collation elements */ ElementIterator.prototype.next = function () { - if (this.elements.length < 1) { - this._fillBuffer(); - } - var ret = this.elements[0]; - this.elements = this.elements.slice(1); - return ret; + if (this.elements.length < 1) { + this._fillBuffer(); + } + var ret = this.elements[0]; + this.elements = this.elements.slice(1); + return ret; }; module.exports = ElementIterator; diff --git a/js/lib/EnergyUnit.js b/js/lib/EnergyUnit.js index abacbb66b5..5547e9c5c8 100644 --- a/js/lib/EnergyUnit.js +++ b/js/lib/EnergyUnit.js @@ -77,12 +77,12 @@ EnergyUnit.ratios = { * @return {string} the name of the type of this measurement */ EnergyUnit.prototype.getMeasure = function() { - return "energy"; + return "energy"; }; /** * Return a new instance of this type of measurement. - * + * * @param {Object} params parameters to the constructor * @return {Measurement} a measurement subclass instance */ diff --git a/js/lib/EthiopicCal.js b/js/lib/EthiopicCal.js index 85bec8d7c4..f69a9f5531 100644 --- a/js/lib/EthiopicCal.js +++ b/js/lib/EthiopicCal.js @@ -25,14 +25,14 @@ var Calendar = require("./Calendar.js"); * @class * Construct a new Ethiopic calendar object. This class encodes information about * a Ethiopic calendar.
- * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var EthiopicCal = function(options) { - this.type = "ethiopic"; - + this.type = "ethiopic"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -40,41 +40,41 @@ var EthiopicCal = function(options) { /** * Return the number of months in the given year. The number of months in a year varies - * for lunar calendars because in some years, an extra month is needed to extend the + * for lunar 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=Maskaram, 2=Teqemt, etc. until 13=Paguemen. - * + * * @param {number} year a year for which the number of months is sought */ EthiopicCal.prototype.getNumMonths = function(year) { - return 13; + return 13; }; /** * 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|string} 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 */ EthiopicCal.prototype.getMonLength = function(month, year) { - var m = month; - switch (typeof(m)) { - case "string": - m = parseInt(m, 10); + var m = month; + switch (typeof(m)) { + case "string": + m = parseInt(m, 10); break; case "function": case "object": case "undefined": return 30; - } - if (m < 13) { - return 30; - } else { - return this.isLeapYear(year) ? 6 : 5; - } + } + if (m < 13) { + return 30; + } else { + return this.isLeapYear(year) ? 6 : 5; + } }; /** @@ -84,8 +84,8 @@ EthiopicCal.prototype.getMonLength = function(month, year) { * @return {boolean} true if the given year is a leap year */ EthiopicCal.prototype.isLeapYear = function(year) { - var y = year; - switch (typeof(y)) { + var y = year; + switch (typeof(y)) { case "string": y = parseInt(y, 10); break; @@ -100,16 +100,16 @@ EthiopicCal.prototype.isLeapYear = function(year) { return false; break; } - return MathUtils.mod(y, 4) === 3; + return MathUtils.mod(y, 4) === 3; }; /** * 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 */ EthiopicCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/EthiopicDate.js b/js/lib/EthiopicDate.js index 723fb8234c..e496ecc3af 100644 --- a/js/lib/EthiopicDate.js +++ b/js/lib/EthiopicDate.js @@ -17,7 +17,7 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var MathUtils = require("./MathUtils.js"); var EthiopicRataDie = require("./EthiopicRataDie.js"); @@ -209,7 +209,7 @@ EthiopicDate.prototype._init = function (params) { * @returns {RataDie} the new RD instance for the given params */ EthiopicDate.prototype.newRd = function (params) { - return new EthiopicRataDie(params); + return new EthiopicRataDie(params); }; /** @@ -219,9 +219,9 @@ EthiopicDate.prototype.newRd = function (params) { * @returns {number} the year for the RD */ EthiopicDate.prototype._calcYear = function(rd) { - var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); + var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); - return year; + return year; }; /** @@ -229,56 +229,56 @@ EthiopicDate.prototype._calcYear = function(rd) { * @protected */ EthiopicDate.prototype._calcDateComponents = function () { - var remainder, - rd = this.rd.getRataDie(); - - this.year = this._calcYear(rd); - - if (typeof(this.offset) === "undefined") { - this.year = this._calcYear(rd); - - // 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); - } - - var jan1 = this.newRd({ - year: this.year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - remainder = rd + 1 - jan1.getRataDie(); - - this.month = Math.floor((remainder-1)/30) + 1; - remainder = remainder - (this.month-1) * 30; - - this.day = Math.floor(remainder); - remainder -= this.day; - // 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") { + this.year = this._calcYear(rd); + + // 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); + } + + var jan1 = this.newRd({ + year: this.year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + remainder = rd + 1 - jan1.getRataDie(); + + this.month = Math.floor((remainder-1)/30) + 1; + remainder = remainder - (this.month-1) * 30; + + this.day = Math.floor(remainder); + remainder -= this.day; + // 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; }; /** @@ -288,8 +288,8 @@ EthiopicDate.prototype._calcDateComponents = function () { * @return {number} the day of the week */ EthiopicDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd-4, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd-4, 7); }; /** @@ -298,7 +298,7 @@ EthiopicDate.prototype.getDayOfWeek = function() { * @return {string} a string giving the name of the calendar */ EthiopicDate.prototype.getCalendar = function() { - return "ethiopic"; + return "ethiopic"; }; //register with the factory method diff --git a/js/lib/EthiopicRataDie.js b/js/lib/EthiopicRataDie.js index 21cf04f0f7..05567744d7 100644 --- a/js/lib/EthiopicRataDie.js +++ b/js/lib/EthiopicRataDie.js @@ -1,6 +1,6 @@ /* * EthiopicRataDie.js - Represent an RD date in the Ethiopic calendar - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,56 +22,56 @@ var RataDie = require("./RataDie.js"); /** * @class - * Construct a new Ethiopic RD date number object. The constructor parameters can + * Construct a new Ethiopic RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Ethiopic date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 Maskaram, 2 means Teqemt, etc., and 13 means Paguemen - * + * *
- 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 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 Ethiopic RD date */ var EthiopicRataDie = function(params) { - this.cal = params && params.cal || new EthiopicCal(); - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new EthiopicCal(); + this.rd = NaN; + RataDie.call(this, params); }; EthiopicRataDie.prototype = new RataDie(); @@ -80,11 +80,11 @@ EthiopicRataDie.prototype.constructor = EthiopicRataDie; /** * The difference between the zero Julian day and the first Ethiopic date - * of Friday, August 29, 8 CE Julian at 6:00am UTC.
- * + * of Friday, August 29, 8 CE Julian at 6:00am UTC.
+ * * See for information about how time is handled in Ethiopia. - * + * * @protected * @type number */ @@ -93,29 +93,29 @@ EthiopicRataDie.prototype.epoch = 1724219.75; /** * Calculate the Rata Die (fixed day) number of the given date from the * date components. - * + * * @protected * @param {Object} date the date components to calculate the RD from */ EthiopicRataDie.prototype._setDateComponents = function(date) { - var year = date.year; - var years = 365 * (year - 1) + Math.floor(year/4); - var dayInYear = (date.month-1) * 30 + date.day; - var rdtime = (date.hour * 3600000 + - date.minute * 60000 + - date.second * 1000 + - date.millisecond) / - 86400000; - - /* - console.log("calcRataDie: converting " + JSON.stringify(parts)); - console.log("getRataDie: year is " + years); - console.log("getRataDie: day in year is " + dayInYear); - console.log("getRataDie: rdtime is " + rdtime); - console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); - */ - - this.rd = years + dayInYear + rdtime; + var year = date.year; + var years = 365 * (year - 1) + Math.floor(year/4); + var dayInYear = (date.month-1) * 30 + date.day; + var rdtime = (date.hour * 3600000 + + date.minute * 60000 + + date.second * 1000 + + date.millisecond) / + 86400000; + + /* + console.log("calcRataDie: converting " + JSON.stringify(parts)); + console.log("getRataDie: year is " + years); + console.log("getRataDie: day in year is " + dayInYear); + console.log("getRataDie: rdtime is " + rdtime); + console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); + */ + + this.rd = years + dayInYear + rdtime; }; module.exports = EthiopicRataDie; diff --git a/js/lib/GlyphString.js b/js/lib/GlyphString.js index 3a4c08eabb..81dfaa478f 100644 --- a/js/lib/GlyphString.js +++ b/js/lib/GlyphString.js @@ -1,7 +1,7 @@ /* - * GlyphString.js - ilib string subclass that allows you to access + * GlyphString.js - ilib string subclass that allows you to access * whole glyphs at a time - * + * * Copyright © 2015-2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ // !data ccc nfc ctype_m -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var JSUtils = require("./JSUtils.js"); @@ -29,10 +29,10 @@ var CType = require("./CType.js"); /** * @class - * Create a new glyph string instance. This string inherits from + * Create a new glyph string instance. This string inherits from * the IString class, and adds methods that allow you to access * whole glyphs at a time.
- * + * * In Unicode, various accented characters can be created by using * a base character and one or more combining characters following * it. These appear on the screen to the user as a single glyph. @@ -40,112 +40,112 @@ var CType = require("./CType.js"); * combining diaresis character "¨" (U+0308) combine together to * form the "a with diaresis" glyph "ä", which looks like a single * character on the screen.
- * + * * The big problem with combining characters for web developers is * that many CSS engines do not ellipsize text between glyphs. They - * only deal with single Unicode characters. So if a particular space + * only deal with single Unicode characters. So if a particular space * only allows for 4 characters, the CSS engine will truncate a * string at 4 Unicode characters and then add the ellipsis (...) * character. What if the fourth Unicode character is the "a" and * the fifth one is the diaresis? Then a string like "xxxäxxx" that - * is ellipsized at 4 characters will appear as "xxxa..." on the + * is ellipsized at 4 characters will appear as "xxxa..." on the * screen instead of "xxxä...".
- * + * * In the Latin script as it is commonly used, it is not so common * to form accented characters using combining accents, so the above * example is mostly for illustrative purposes. It is not unheard of - * however. The situation is much, much worse in scripts such as Thai and + * however. The situation is much, much worse in scripts such as Thai and * Devanagari that normally make very heavy use of combining characters. - * These scripts do so because Unicode does not include pre-composed - * versions of the accented characters like it does for Latin, so - * combining accents are the only way to create these accented and + * These scripts do so because Unicode does not include pre-composed + * versions of the accented characters like it does for Latin, so + * combining accents are the only way to create these accented and * combined versions of the characters.
- * - * The solution to this problem is not to use the the CSS property + * + * The solution to this problem is not to use the the CSS property * "text-overflow: ellipsis" in your web site, ever. Instead, use * a glyph string to truncate text between glyphs dynamically, * rather than truncating between Unicode characters using CSS.
- * - * Glyph strings are also useful for truncation, hyphenation, and + * + * Glyph strings are also useful for truncation, hyphenation, and * line wrapping, as all of these should be done between glyphs instead * of between characters.
- * + * * The options parameter is optional, and may contain any combination * of the following properties:
- * + * *
*
- * + * * @constructor * @extends IString - * @param {string|IString=} str initialize this instance with this string + * @param {string|IString=} str initialize this instance with this string * @param {Object=} options options governing the way this instance works */ var GlyphString = function (str, options) { - if (options && options.noinstance) { - return; - } - - IString.call(this, str); - - options = options || {sync: true}; - - CType._load("ctype_m", options.sync, options.loadParams, ilib.bind(this, function() { - if (!ilib.data.ccc || JSUtils.isEmpty(ilib.data.ccc)) { - Utils.loadData({ - object: "GlyphString", - locale: "-", - name: "ccc.json", - nonlocale: true, - sync: options.sync, - loadParams: options.loadParams, - callback: ilib.bind(this, function (norm) { - ilib.data.ccc = norm; - if (!ilib.data.norm.nfc || JSUtils.isEmpty(ilib.data.norm.nfc)) { - Utils.loadData({ - object: "GlyphString", - locale: "-", - name: "nfc/all.json", - nonlocale: true, - sync: options.sync, - loadParams: options.loadParams, - callback: ilib.bind(this, function (norm) { - ilib.data.norm.nfc = norm; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } - }) - }); - } else { - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - } - })); + if (options && options.noinstance) { + return; + } + + IString.call(this, str); + + options = options || {sync: true}; + + CType._load("ctype_m", options.sync, options.loadParams, ilib.bind(this, function() { + if (!ilib.data.ccc || JSUtils.isEmpty(ilib.data.ccc)) { + Utils.loadData({ + object: "GlyphString", + locale: "-", + name: "ccc.json", + nonlocale: true, + sync: options.sync, + loadParams: options.loadParams, + callback: ilib.bind(this, function (norm) { + ilib.data.ccc = norm; + if (!ilib.data.norm.nfc || JSUtils.isEmpty(ilib.data.norm.nfc)) { + Utils.loadData({ + object: "GlyphString", + locale: "-", + name: "nfc/all.json", + nonlocale: true, + sync: options.sync, + loadParams: options.loadParams, + callback: ilib.bind(this, function (norm) { + ilib.data.norm.nfc = norm; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } + }) + }); + } else { + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + } + })); }; GlyphString.prototype = new IString(undefined); @@ -154,47 +154,47 @@ GlyphString.prototype.constructor = GlyphString; /** * Return true if the given character is a leading Jamo (Choseong) character. - * + * * @private * @static * @param {number} n code point to check - * @return {boolean} true if the character is a leading Jamo character, + * @return {boolean} true if the character is a leading Jamo character, * false otherwise */ GlyphString._isJamoL = function (n) { - return (n >= 0x1100 && n <= 0x1112); + return (n >= 0x1100 && n <= 0x1112); }; /** * Return true if the given character is a vowel Jamo (Jungseong) character. - * + * * @private * @static * @param {number} n code point to check - * @return {boolean} true if the character is a vowel Jamo character, + * @return {boolean} true if the character is a vowel Jamo character, * false otherwise */ GlyphString._isJamoV = function (n) { - return (n >= 0x1161 && n <= 0x1175); + return (n >= 0x1161 && n <= 0x1175); }; /** * Return true if the given character is a trailing Jamo (Jongseong) character. - * + * * @private * @static * @param {number} n code point to check - * @return {boolean} true if the character is a trailing Jamo character, + * @return {boolean} true if the character is a trailing Jamo character, * false otherwise */ GlyphString._isJamoT = function (n) { - return (n >= 0x11A8 && n <= 0x11C2); + return (n >= 0x11A8 && n <= 0x11C2); }; /** * Return true if the given character is a LV Jamo character. * LV Jamo character is a precomposed Hangul character with LV sequence. - * + * * @private * @static * @param {number} n code point to check @@ -202,41 +202,41 @@ GlyphString._isJamoT = function (n) { * false otherwise */ GlyphString._isJamoLV = function (n) { - var syllableBase = 0xAC00; - var leadingJamoCount = 19; - var vowelJamoCount = 21; - var trailingJamoCount = 28; - var syllableCount = leadingJamoCount * vowelJamoCount * trailingJamoCount; - var syllableIndex = n - syllableBase; - // Check if n is a precomposed Hangul - if (0 <= syllableIndex && syllableIndex < syllableCount) { - // Check if n is a LV Jamo character - if((syllableIndex % trailingJamoCount) == 0) { - return true; - } - } - return false; + var syllableBase = 0xAC00; + var leadingJamoCount = 19; + var vowelJamoCount = 21; + var trailingJamoCount = 28; + var syllableCount = leadingJamoCount * vowelJamoCount * trailingJamoCount; + var syllableIndex = n - syllableBase; + // Check if n is a precomposed Hangul + if (0 <= syllableIndex && syllableIndex < syllableCount) { + // Check if n is a LV Jamo character + if((syllableIndex % trailingJamoCount) == 0) { + return true; + } + } + return false; }; /** * Return true if the given character is a precomposed Hangul character. * The precomposed Hangul character may be a LV Jamo character or a LVT Jamo Character. - * + * * @private * @static * @param {number} n code point to check - * @return {boolean} true if the character is a precomposed Hangul character, + * @return {boolean} true if the character is a precomposed Hangul character, * false otherwise */ GlyphString._isHangul = function (n) { - return (n >= 0xAC00 && n <= 0xD7A3); + return (n >= 0xAC00 && n <= 0xD7A3); }; /** * Algorithmically compose an L and a V combining Jamo characters into * a precomposed Korean syllabic Hangul character. Both should already - * be in the proper ranges for L and V characters. - * + * be in the proper ranges for L and V characters. + * * @private * @static * @param {number} lead the code point of the lead Jamo character to compose @@ -244,15 +244,15 @@ GlyphString._isHangul = function (n) { * @return {string} the composed Hangul character */ GlyphString._composeJamoLV = function (lead, trail) { - var lindex = lead - 0x1100; - var vindex = trail - 0x1161; - return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); + var lindex = lead - 0x1100; + var vindex = trail - 0x1161; + return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); }; /** - * Algorithmically compose a Hangul LV and a combining Jamo T character - * into a precomposed Korean syllabic Hangul character. - * + * Algorithmically compose a Hangul LV and a combining Jamo T character + * into a precomposed Korean syllabic Hangul character. + * * @private * @static * @param {number} lead the code point of the lead Hangul character to compose @@ -260,15 +260,15 @@ GlyphString._composeJamoLV = function (lead, trail) { * @return {string} the composed Hangul character */ GlyphString._composeJamoLVT = function (lead, trail) { - return IString.fromCodePoint(lead + (trail - 0x11A7)); + return IString.fromCodePoint(lead + (trail - 0x11A7)); }; /** - * Compose one character out of a leading character and a + * Compose one character out of a leading character and a * trailing character. If the characters are Korean Jamo, they * will be composed algorithmically. If they are any other * characters, they will be looked up in the nfc tables. - * + * * @private * @static * @param {string} lead leading character to compose @@ -277,162 +277,162 @@ GlyphString._composeJamoLVT = function (lead, trail) { * there is no composition for those two characters */ GlyphString._compose = function (lead, trail) { - var first = lead.charCodeAt(0); - var last = trail.charCodeAt(0); - if (GlyphString._isJamoLV(first) && GlyphString._isJamoT(last)) { - return GlyphString._composeJamoLVT(first, last); - } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { - return GlyphString._composeJamoLV(first, last); - } + var first = lead.charCodeAt(0); + var last = trail.charCodeAt(0); + if (GlyphString._isJamoLV(first) && GlyphString._isJamoT(last)) { + return GlyphString._composeJamoLVT(first, last); + } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { + return GlyphString._composeJamoLV(first, last); + } - var c = lead + trail; - return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); + var c = lead + trail; + return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); }; /** * Return an iterator that will step through all of the characters - * in the string one at a time, taking care to step through decomposed - * characters and through surrogate pairs in the UTF-16 encoding + * in the string one at a time, taking care to step through decomposed + * characters and through surrogate pairs in the UTF-16 encoding * as single characters.- onLoad - a callback function to call when the locale data are * 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. - * - *
- 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 GlyphString class will return decomposed Unicode characters * as a single unit that a user might see on the screen as a single - * glyph. If the - * next character in the iteration is a base character and it is - * followed by combining characters, the base and all its following + * glyph. If the + * next character in the iteration is a base character and it is + * followed by combining characters, the base and all its following * combining characters are returned as a single unit.
- * + * * The standard Javascript String's charAt() method only - * returns information about a particular 16-bit character in the + * returns information about a particular 16-bit character in the * UTF-16 encoding scheme. * If the index is pointing to a low- or high-surrogate character, - * it will return that surrogate character rather - * than the surrogate pair which represents a character + * it will return that surrogate character rather + * than the surrogate pair which represents a character * in the supplementary planes.
- * + * * The iterator instance returned has two methods, hasNext() which * returns true if the iterator has more characters to iterate through, * and next() which returns the next character.
- * + * * @override - * @return {Object} an iterator + * @return {Object} an iterator * that iterates through all the characters in the string */ GlyphString.prototype.charIterator = function() { - var it = IString.prototype.charIterator.call(this); - - /** - * @constructor - */ - function _chiterator (istring) { - this.index = 0; - this.spacingCombining = false; - this.hasNext = function () { - return !!this.nextChar || it.hasNext(); - }; - this.next = function () { - var ch = this.nextChar || it.next(), - prevCcc = ilib.data.ccc[ch], - nextCcc, - composed = ch; - - this.nextChar = undefined; - this.spacingCombining = false; - - if (ilib.data.ccc && - (typeof(ilib.data.ccc[ch]) === 'undefined' || ilib.data.ccc[ch] === 0)) { - // found a starter... find all the non-starters until the next starter. Must include - // the next starter because under some odd circumstances, two starters sometimes recompose - // together to form another character - var notdone = true; - while (it.hasNext() && notdone) { - this.nextChar = it.next(); - nextCcc = ilib.data.ccc[this.nextChar]; - var codePoint = IString.toCodePoint(this.nextChar, 0); - // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be - // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing - // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than - // just the base character alone. - var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); - var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); - if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { - if (isMc) { - this.spacingCombining = true; - } - ch += this.nextChar; - this.nextChar = undefined; - } else { - // found the next starter. See if this can be composed with the previous starter - var testChar = GlyphString._compose(composed, this.nextChar); - if (prevCcc === 0 && typeof(testChar) !== 'undefined') { - // not blocked and there is a mapping - composed = testChar; - ch += this.nextChar; - this.nextChar = undefined; - } else { - // finished iterating, leave this.nextChar for the next next() call - notdone = false; - } - } - prevCcc = nextCcc; - } - } - return ch; - }; - // Returns true if the last character returned by the "next" method included - // spacing combining characters. If it does, then the character was wider than - // just the base character alone, and the truncation code will not add it. - this.wasSpacingCombining = function() { - return this.spacingCombining; - }; - }; - return new _chiterator(this); + var it = IString.prototype.charIterator.call(this); + + /** + * @constructor + */ + function _chiterator (istring) { + this.index = 0; + this.spacingCombining = false; + this.hasNext = function () { + return !!this.nextChar || it.hasNext(); + }; + this.next = function () { + var ch = this.nextChar || it.next(), + prevCcc = ilib.data.ccc[ch], + nextCcc, + composed = ch; + + this.nextChar = undefined; + this.spacingCombining = false; + + if (ilib.data.ccc && + (typeof(ilib.data.ccc[ch]) === 'undefined' || ilib.data.ccc[ch] === 0)) { + // found a starter... find all the non-starters until the next starter. Must include + // the next starter because under some odd circumstances, two starters sometimes recompose + // together to form another character + var notdone = true; + while (it.hasNext() && notdone) { + this.nextChar = it.next(); + nextCcc = ilib.data.ccc[this.nextChar]; + var codePoint = IString.toCodePoint(this.nextChar, 0); + // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be + // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing + // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than + // just the base character alone. + var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); + var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); + if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { + if (isMc) { + this.spacingCombining = true; + } + ch += this.nextChar; + this.nextChar = undefined; + } else { + // found the next starter. See if this can be composed with the previous starter + var testChar = GlyphString._compose(composed, this.nextChar); + if (prevCcc === 0 && typeof(testChar) !== 'undefined') { + // not blocked and there is a mapping + composed = testChar; + ch += this.nextChar; + this.nextChar = undefined; + } else { + // finished iterating, leave this.nextChar for the next next() call + notdone = false; + } + } + prevCcc = nextCcc; + } + } + return ch; + }; + // Returns true if the last character returned by the "next" method included + // spacing combining characters. If it does, then the character was wider than + // just the base character alone, and the truncation code will not add it. + this.wasSpacingCombining = function() { + return this.spacingCombining; + }; + }; + return new _chiterator(this); }; /** * Truncate the current string at the given number of whole glyphs and return * the resulting string. - * + * * @param {number} length the number of whole glyphs to keep in the string * @return {string} a string truncated to the requested number of glyphs */ GlyphString.prototype.truncate = function(length) { - var it = this.charIterator(); - var tr = ""; - for (var i = 0; i < length-1 && it.hasNext(); i++) { - tr += it.next(); - } - - /* - * handle the last character separately. If it contains spacing combining - * accents, then we must assume that it uses up more horizontal space on - * the screen than just the base character by itself, and therefore this - * method will not truncate enough characters to fit in the given length. - * In this case, we have to chop off not only the combining characters, - * but also the base character as well because the base without the - * combining accents is considered a different character. - */ - if (i < length && it.hasNext()) { - var c = it.next(); - if (!it.wasSpacingCombining()) { - tr += c; - } - } - return tr; + var it = this.charIterator(); + var tr = ""; + for (var i = 0; i < length-1 && it.hasNext(); i++) { + tr += it.next(); + } + + /* + * handle the last character separately. If it contains spacing combining + * accents, then we must assume that it uses up more horizontal space on + * the screen than just the base character by itself, and therefore this + * method will not truncate enough characters to fit in the given length. + * In this case, we have to chop off not only the combining characters, + * but also the base character as well because the base without the + * combining accents is considered a different character. + */ + if (i < length && it.hasNext()) { + var c = it.next(); + if (!it.wasSpacingCombining()) { + tr += c; + } + } + return tr; }; /** * Truncate the current string at the given number of glyphs and add an ellipsis * to indicate that is more to the string. The ellipsis forms the last character * in the string, so the string is actually truncated at length-1 glyphs. - * - * @param {number} length the number of whole glyphs to keep in the string + * + * @param {number} length the number of whole glyphs to keep in the string * including the ellipsis * @return {string} a string truncated to the requested number of glyphs * with an ellipsis */ GlyphString.prototype.ellipsize = function(length) { - return this.truncate(length > 0 ? length-1 : 0) + "…"; + return this.truncate(length > 0 ? length-1 : 0) + "…"; }; module.exports = GlyphString; diff --git a/js/lib/GregRataDie.js b/js/lib/GregRataDie.js index 8ee497e977..3af4c55330 100644 --- a/js/lib/GregRataDie.js +++ b/js/lib/GregRataDie.js @@ -1,6 +1,6 @@ /* * GregRataDie.js - Represent the RD date number in the Gregorian calendar - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,57 +23,57 @@ var RataDie = require("./RataDie.js"); /** * @class - * Construct a new Gregorian RD date number object. The constructor parameters can + * Construct a new Gregorian RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Gregorian date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * + * *
- date - use the given intrinsic Javascript date to initialize this one. *
- * - * 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 Gregorian RD date */ var GregRataDie = function(params) { - this.cal = params && params.cal || new GregorianCal(); - /** @type {number|undefined} */ - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new GregorianCal(); + /** @type {number|undefined} */ + this.rd = NaN; + RataDie.call(this, params); }; GregRataDie.prototype = new RataDie(); @@ -81,102 +81,102 @@ GregRataDie.prototype.parent = RataDie; GregRataDie.prototype.constructor = GregRataDie; /** - * the cumulative lengths of each month, for a non-leap year + * the cumulative lengths of each month, for a non-leap year * @private * @const * @type Array.
and the two are not to be confused. The Julian Day + * object represents time as a number of whole and fractional days since the + * beginning of the epoch, whereas a date in the Julian * calendar is a regular date that signifies year, month, day, etc. using the rules * of the Julian calendar. The naming of Julian Days and the Julian calendar are * unfortunately close, and come from history.*/ GregRataDie.cumMonthLengths = [ 0, /* Jan */ - 31, /* Feb */ - 59, /* Mar */ - 90, /* Apr */ - 120, /* May */ - 151, /* Jun */ - 181, /* Jul */ - 212, /* Aug */ - 243, /* Sep */ - 273, /* Oct */ - 304, /* Nov */ - 334, /* Dec */ - 365 + 31, /* Feb */ + 59, /* Mar */ + 90, /* Apr */ + 120, /* May */ + 151, /* Jun */ + 181, /* Jul */ + 212, /* Aug */ + 243, /* Sep */ + 273, /* Oct */ + 304, /* Nov */ + 334, /* Dec */ + 365 ]; /** - * the cumulative lengths of each month, for a leap year + * the cumulative lengths of each month, for a leap year * @private * @const * @type Array. */ GregRataDie.cumMonthLengthsLeap = [ - 0, /* Jan */ - 31, /* Feb */ - 60, /* Mar */ - 91, /* Apr */ - 121, /* May */ - 152, /* Jun */ - 182, /* Jul */ - 213, /* Aug */ - 244, /* Sep */ - 274, /* Oct */ - 305, /* Nov */ - 335, /* Dec */ - 366 + 0, /* Jan */ + 31, /* Feb */ + 60, /* Mar */ + 91, /* Apr */ + 121, /* May */ + 152, /* Jun */ + 182, /* Jul */ + 213, /* Aug */ + 244, /* Sep */ + 274, /* Oct */ + 305, /* Nov */ + 335, /* Dec */ + 366 ]; /** * Calculate the Rata Die (fixed day) number of the given date. - * + * * @private * @param {Object} date the date components to calculate the RD from */ GregRataDie.prototype._setDateComponents = function(date) { - var year = parseInt(date.year, 10) || 0; - var month = parseInt(date.month, 10) || 1; - var day = parseInt(date.day, 10) || 1; - var hour = parseInt(date.hour, 10) || 0; - var minute = parseInt(date.minute, 10) || 0; - var second = parseInt(date.second, 10) || 0; - var millisecond = parseInt(date.millisecond, 10) || 0; + var year = parseInt(date.year, 10) || 0; + var month = parseInt(date.month, 10) || 1; + var day = parseInt(date.day, 10) || 1; + var hour = parseInt(date.hour, 10) || 0; + var minute = parseInt(date.minute, 10) || 0; + var second = parseInt(date.second, 10) || 0; + var millisecond = parseInt(date.millisecond, 10) || 0; + + var years = 365 * (year - 1) + + Math.floor((year-1)/4) - + Math.floor((year-1)/100) + + Math.floor((year-1)/400); + + var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + + day + + (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); + var rdtime = (hour * 3600000 + + minute * 60000 + + second * 1000 + + millisecond) / + 86400000; + /* + debug("getRataDie: converting " + JSON.stringify(this)); + debug("getRataDie: year is " + years); + debug("getRataDie: day in year is " + dayInYear); + debug("getRataDie: rdtime is " + rdtime); + debug("getRataDie: rd is " + (years + dayInYear + rdtime)); + */ - var years = 365 * (year - 1) + - Math.floor((year-1)/4) - - Math.floor((year-1)/100) + - Math.floor((year-1)/400); - - var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + - day + - (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); - var rdtime = (hour * 3600000 + - minute * 60000 + - second * 1000 + - millisecond) / - 86400000; - /* - debug("getRataDie: converting " + JSON.stringify(this)); - debug("getRataDie: year is " + years); - debug("getRataDie: day in year is " + dayInYear); - debug("getRataDie: rdtime is " + rdtime); - debug("getRataDie: rd is " + (years + dayInYear + rdtime)); - */ - - /** - * @type {number|undefined} the RD number of this Gregorian date - */ - this.rd = years + dayInYear + rdtime; + /** + * @type {number|undefined} the RD number of this Gregorian date + */ + this.rd = years + 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 */ GregRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); }; module.exports = GregRataDie; diff --git a/js/lib/GregorianCal.js b/js/lib/GregorianCal.js index f5c34ba0ad..a06a34bd10 100644 --- a/js/lib/GregorianCal.js +++ b/js/lib/GregorianCal.js @@ -1,6 +1,6 @@ /* * GregorianCal.js - Represent a Gregorian calendar object. - * + * * Copyright © 2012-2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,71 +26,71 @@ var Calendar = require("./Calendar.js"); * @class * Construct a new Gregorian calendar object. This class encodes information about * a Gregorian calendar. - * - * + * + * * @constructor * @param {{noinstance:boolean}=} options * @extends Calendar */ var GregorianCal = function(options) { - if (!options || !options.noinstance) { - this.type = "gregorian"; - } - - if (options && typeof(options.onLoad) === "function") { - options.onLoad(this); - } + if (!options || !options.noinstance) { + this.type = "gregorian"; + } + + if (options && typeof(options.onLoad) === "function") { + options.onLoad(this); + } }; /** - * the lengths of each month + * the lengths of each month * @private * @const - * @type Array.
+ * @type Array. */ GregorianCal.monthLengths = [ - 31, /* Jan */ - 28, /* Feb */ - 31, /* Mar */ - 30, /* Apr */ - 31, /* May */ - 30, /* Jun */ - 31, /* Jul */ - 31, /* Aug */ - 30, /* Sep */ - 31, /* Oct */ - 30, /* Nov */ - 31 /* Dec */ + 31, /* Jan */ + 28, /* Feb */ + 31, /* Mar */ + 30, /* Apr */ + 31, /* May */ + 30, /* Jun */ + 31, /* Jul */ + 31, /* Aug */ + 30, /* Sep */ + 31, /* Oct */ + 30, /* Nov */ + 31 /* Dec */ ]; /** * 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 */ GregorianCal.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 */ GregorianCal.prototype.getMonLength = function(month, year) { - if (month !== 2 || !this.isLeapYear(year)) { - return GregorianCal.monthLengths[month-1]; - } else { - return 29; - } + if (month !== 2 || !this.isLeapYear(year)) { + return GregorianCal.monthLengths[month-1]; + } else { + return 29; + } }; /** @@ -100,18 +100,18 @@ GregorianCal.prototype.getMonLength = function(month, year) { * @return {boolean} true if the given year is a leap year */ GregorianCal.prototype.isLeapYear = function(year) { - var y = (typeof(year) === 'number' ? year : year.getYears()); - 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()); + var centuries = MathUtils.mod(y, 400); + return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); }; /** * 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 */ GregorianCal.prototype.getType = function() { - return this.type; + return this.type; }; /* register this calendar for the factory method */ diff --git a/js/lib/GregorianDate.js b/js/lib/GregorianDate.js index d6502bcc9d..08f9a7ec12 100644 --- a/js/lib/GregorianDate.js +++ b/js/lib/GregorianDate.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"); @@ -251,7 +251,7 @@ GregorianDate.prototype._init2 = function (params) { * @returns {RataDie} the new RD instance for the given params */ GregorianDate.prototype.newRd = function (params) { - return new GregRataDie(params); + return new GregRataDie(params); }; /** @@ -260,35 +260,35 @@ GregorianDate.prototype.newRd = function (params) { * @static */ GregorianDate._calcYear = function(rd) { - var days400, - days100, - days4, - years400, - years100, - years4, - years1, - year; - - years400 = Math.floor((rd - 1) / 146097); - days400 = MathUtils.mod((rd - 1), 146097); - years100 = Math.floor(days400 / 36524); - days100 = MathUtils.mod(days400, 36524); - years4 = Math.floor(days100 / 1461); - days4 = MathUtils.mod(days100, 1461); - years1 = Math.floor(days4 / 365); - - year = 400 * years400 + 100 * years100 + 4 * years4 + years1; - if (years100 !== 4 && years1 !== 4) { - year++; - } - return year; + var days400, + days100, + days4, + years400, + years100, + years4, + years1, + year; + + years400 = Math.floor((rd - 1) / 146097); + days400 = MathUtils.mod((rd - 1), 146097); + years100 = Math.floor(days400 / 36524); + days100 = MathUtils.mod(days400, 36524); + years4 = Math.floor(days100 / 1461); + days4 = MathUtils.mod(days100, 1461); + years1 = Math.floor(days4 / 365); + + year = 400 * years400 + 100 * years100 + 4 * years4 + years1; + if (years100 !== 4 && years1 !== 4) { + year++; + } + return year; }; /** * @private */ GregorianDate.prototype._calcYear = function(rd) { - return GregorianDate._calcYear(rd); + return GregorianDate._calcYear(rd); }; /** @@ -296,113 +296,113 @@ GregorianDate.prototype._calcYear = function(rd) { * @private */ GregorianDate.prototype._calcDateComponents = function () { - if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { - // console.log("using js Date to calculate offset"); - // use the intrinsic JS Date object to do the tz conversion for us, which - // guarantees that it follows the system tz database settings - var d = new Date(this.rd.getTimeExtended()); - - /** - * Year in the Gregorian calendar. - * @type number - */ - this.year = d.getFullYear(); - - /** - * The month number, ranging from 1 (January) to 12 (December). - * @type number - */ - this.month = d.getMonth()+1; - - /** - * The day of the month. This ranges from 1 to 31. - * @type number - */ - this.day = d.getDate(); - - /** - * The hour of the day. This can be a number from 0 to 23, as times are - * stored unambiguously in the 24-hour clock. - * @type number - */ - this.hour = d.getHours(); - - /** - * The minute of the hours. Ranges from 0 to 59. - * @type number - */ - this.minute = d.getMinutes(); - - /** - * The second of the minute. Ranges from 0 to 59. - * @type number - */ - this.second = d.getSeconds(); - - /** - * The millisecond of the second. Ranges from 0 to 999. - * @type number - */ - this.millisecond = d.getMilliseconds(); - - this.offset = -d.getTimezoneOffset() / 1440; - } else { - // console.log("using ilib to calculate offset. tz is " + this.timezone); - // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); - if (typeof(this.offset) === "undefined") { - // console.log("calculating offset"); - this.year = this._calcYear(this.rd.getRataDie()); - - // 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; - // } else { - // console.log("offset is already defined somehow. type is " + typeof(this.offset)); - // console.trace("Stack is this one"); - } - // console.log("offset is " + this.offset); - var rd = this.rd.getRataDie(); - if (this.offset !== 0) { - rd += this.offset; - } - this.year = this._calcYear(rd); - - var yearStartRd = this.newRd({ - year: this.year, - month: 1, - day: 1, - cal: this.cal - }); - - // remainder is days into the year - var remainder = rd - yearStartRd.getRataDie() + 1; - - var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? - GregRataDie.cumMonthLengthsLeap : - GregRataDie.cumMonthLengths; - - this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); - remainder = remainder - cumulative[this.month-1]; - - this.day = Math.floor(remainder); - remainder -= this.day; - // 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 = Math.floor(remainder); - } + if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { + // console.log("using js Date to calculate offset"); + // use the intrinsic JS Date object to do the tz conversion for us, which + // guarantees that it follows the system tz database settings + var d = new Date(this.rd.getTimeExtended()); + + /** + * Year in the Gregorian calendar. + * @type number + */ + this.year = d.getFullYear(); + + /** + * The month number, ranging from 1 (January) to 12 (December). + * @type number + */ + this.month = d.getMonth()+1; + + /** + * The day of the month. This ranges from 1 to 31. + * @type number + */ + this.day = d.getDate(); + + /** + * The hour of the day. This can be a number from 0 to 23, as times are + * stored unambiguously in the 24-hour clock. + * @type number + */ + this.hour = d.getHours(); + + /** + * The minute of the hours. Ranges from 0 to 59. + * @type number + */ + this.minute = d.getMinutes(); + + /** + * The second of the minute. Ranges from 0 to 59. + * @type number + */ + this.second = d.getSeconds(); + + /** + * The millisecond of the second. Ranges from 0 to 999. + * @type number + */ + this.millisecond = d.getMilliseconds(); + + this.offset = -d.getTimezoneOffset() / 1440; + } else { + // console.log("using ilib to calculate offset. tz is " + this.timezone); + // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); + if (typeof(this.offset) === "undefined") { + // console.log("calculating offset"); + this.year = this._calcYear(this.rd.getRataDie()); + + // 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; + // } else { + // console.log("offset is already defined somehow. type is " + typeof(this.offset)); + // console.trace("Stack is this one"); + } + // console.log("offset is " + this.offset); + var rd = this.rd.getRataDie(); + if (this.offset !== 0) { + rd += this.offset; + } + this.year = this._calcYear(rd); + + var yearStartRd = this.newRd({ + year: this.year, + month: 1, + day: 1, + cal: this.cal + }); + + // remainder is days into the year + var remainder = rd - yearStartRd.getRataDie() + 1; + + var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? + GregRataDie.cumMonthLengthsLeap : + GregRataDie.cumMonthLengths; + + this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); + remainder = remainder - cumulative[this.month-1]; + + this.day = Math.floor(remainder); + remainder -= this.day; + // 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 = Math.floor(remainder); + } }; /** @@ -412,8 +412,8 @@ GregorianDate.prototype._calcDateComponents = function () { * @return {number} the day of the week */ GregorianDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd, 7); }; /** @@ -423,11 +423,11 @@ GregorianDate.prototype.getDayOfWeek = function() { * @return {number} the ordinal day of the year */ GregorianDate.prototype.getDayOfYear = function() { - var cumulativeMap = this.cal.isLeapYear(this.year) ? - GregRataDie.cumMonthLengthsLeap : - GregRataDie.cumMonthLengths; + var cumulativeMap = this.cal.isLeapYear(this.year) ? + GregRataDie.cumMonthLengthsLeap : + GregRataDie.cumMonthLengths; - return cumulativeMap[this.month-1] + this.day; + return cumulativeMap[this.month-1] + this.day; }; /** @@ -441,7 +441,7 @@ GregorianDate.prototype.getDayOfYear = function() { * common era */ GregorianDate.prototype.getEra = function() { - return (this.year < 1) ? -1 : 1; + return (this.year < 1) ? -1 : 1; }; /** @@ -450,7 +450,7 @@ GregorianDate.prototype.getEra = function() { * @return {string} a string giving the name of the calendar */ GregorianDate.prototype.getCalendar = function() { - return "gregorian"; + return "gregorian"; }; // register with the factory method diff --git a/js/lib/HanCal.js b/js/lib/HanCal.js index 182a7257c5..507c55b15d 100644 --- a/js/lib/HanCal.js +++ b/js/lib/HanCal.js @@ -1,6 +1,6 @@ /* * HanCal.js - Represent a Han Chinese Lunar calendar object. - * + * * Copyright © 2014-2017, 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 Calendar = require("./Calendar.js"); @@ -31,21 +31,21 @@ var GregRataDie = require("./GregRataDie.js"); * @class * Construct a new Han algorithmic calendar object. This class encodes information about * a Han algorithmic calendar. - * - * + * + * * @constructor * @param {Object=} params optional parameters to load the calendrical data * @extends Calendar */ var HanCal = function(params) { - this.type = "han"; - var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; - - Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) { - if (params && typeof(params.onLoad) === 'function') { - params.onLoad(this); - } - })); + this.type = "han"; + var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; + + Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) { + if (params && typeof(params.onLoad) === 'function') { + params.onLoad(this); + } + })); }; /** @@ -56,39 +56,39 @@ var HanCal = function(params) { * @return {number} */ HanCal._getElapsedYear = function(year, cycle) { - var elapsedYear = year || 0; - if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { - elapsedYear = 60 * cycle + year; - } - return elapsedYear; + var elapsedYear = year || 0; + if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { + elapsedYear = 60 * cycle + year; + } + return elapsedYear; }; /** * @protected * @static * @param {number} jd julian day to calculate from - * @param {number} longitude longitude to seek - * @returns {number} the julian day of the next time that the solar longitude + * @param {number} longitude longitude to seek + * @returns {number} the julian day of the next time that the solar longitude * is a multiple of the given longitude */ HanCal._hanNextSolarLongitude = function(jd, longitude) { - var tz = HanCal._chineseTZ(jd); - var uni = Astro._universalFromLocal(jd, tz); - var sol = Astro._nextSolarLongitude(uni, longitude); - return Astro._localFromUniversal(sol, tz); + var tz = HanCal._chineseTZ(jd); + var uni = Astro._universalFromLocal(jd, tz); + var sol = Astro._nextSolarLongitude(uni, longitude); + return Astro._localFromUniversal(sol, tz); }; /** * @protected * @static - * @param {number} jd julian day to calculate from + * @param {number} jd julian day to calculate from * @returns {number} the major solar term for the julian day */ HanCal._majorSTOnOrAfter = function(jd) { - var tz = HanCal._chineseTZ(jd); - var uni = Astro._universalFromLocal(jd, tz); - var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); - return HanCal._hanNextSolarLongitude(jd, next); + var tz = HanCal._chineseTZ(jd); + var uni = Astro._universalFromLocal(jd, tz); + var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); + return HanCal._hanNextSolarLongitude(jd, next); }; /** @@ -100,18 +100,18 @@ HanCal._majorSTOnOrAfter = function(jd) { * of the epoch */ HanCal._solsticeBefore = function (year, cycle) { - var elapsedYear = HanCal._getElapsedYear(year, cycle); - var gregyear = elapsedYear - 2697; - var rd = new GregRataDie({ - year: gregyear-1, - month: 12, - day: 15, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); + var elapsedYear = HanCal._getElapsedYear(year, cycle); + var gregyear = elapsedYear - 2697; + var rd = new GregRataDie({ + year: gregyear-1, + month: 12, + day: 15, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); }; /** @@ -121,36 +121,36 @@ HanCal._solsticeBefore = function (year, cycle) { * @returns {number} the current major solar term */ HanCal._chineseTZ = function(jd) { - var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); - return year < 1929 ? 465.6666666666666666 : 480; + var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); + return year < 1929 ? 465.6666666666666666 : 480; }; /** * @protected * @static - * @param {number} jd julian day to calculate from + * @param {number} jd julian day to calculate from * @returns {number} the julian day of next new moon on or after the given julian day date */ HanCal._newMoonOnOrAfter = function(jd) { - var tz = HanCal._chineseTZ(jd); - var uni = Astro._universalFromLocal(jd, tz); - var moon = Astro._newMoonAtOrAfter(uni); - // floor to the start of the julian day - return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); + var tz = HanCal._chineseTZ(jd); + var uni = Astro._universalFromLocal(jd, tz); + var moon = Astro._newMoonAtOrAfter(uni); + // floor to the start of the julian day + return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); }; /** * @protected * @static - * @param {number} jd julian day to calculate from + * @param {number} jd julian day to calculate from * @returns {number} the julian day of previous new moon before the given julian day date */ HanCal._newMoonBefore = function(jd) { - var tz = HanCal._chineseTZ(jd); - var uni = Astro._universalFromLocal(jd, tz); - var moon = Astro._newMoonBefore(uni); - // floor to the start of the julian day - return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); + var tz = HanCal._chineseTZ(jd); + var uni = Astro._universalFromLocal(jd, tz); + var moon = Astro._newMoonBefore(uni); + // floor to the start of the julian day + return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); }; /** @@ -162,16 +162,16 @@ HanCal._newMoonBefore = function(jd) { * of the epoch */ HanCal._leapYearCalc = function(year, cycle) { - var ret = { - elapsedYear: HanCal._getElapsedYear(year, cycle) - }; - ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); - ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); - // ceil to the end of the julian day - ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); - ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); - - return ret; + var ret = { + elapsedYear: HanCal._getElapsedYear(year, cycle) + }; + ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); + ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); + // ceil to the end of the julian day + ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); + ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); + + return ret; }; /** @@ -181,8 +181,8 @@ HanCal._leapYearCalc = function(year, cycle) { * @returns {number} the current major solar term */ HanCal._currentMajorST = function(jd) { - var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); - return MathUtils.amod(2 + Math.floor(s/30), 12); + var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); + return MathUtils.amod(2 + Math.floor(s/30), 12); }; /** @@ -192,15 +192,15 @@ HanCal._currentMajorST = function(jd) { * @returns {boolean} true if there is no major solar term in the same year */ HanCal._noMajorST = function(jd) { - return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); + return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); }; /** * 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 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the * cycle is not given, then the year should be given as elapsed years since the beginning @@ -208,49 +208,49 @@ HanCal._noMajorST = function(jd) { * @return {number} The number of months in the given year */ HanCal.prototype.getNumMonths = function(year, cycle) { - return this.isLeapYear(year, cycle) ? 13 : 12; + return this.isLeapYear(year, cycle) ? 13 : 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 elapsed month for which the length is sought * @param {number} year the elapsed year within which that month can be found * @return {number} the number of days within the given month in the given year */ HanCal.prototype.getMonLength = function(month, year) { - // distance between two new moons in Nanjing China - var calc = HanCal._leapYearCalc(year); - var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); - var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); - return postNewMoon - priorNewMoon; + // distance between two new moons in Nanjing China + var calc = HanCal._leapYearCalc(year); + var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); + var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); + return postNewMoon - priorNewMoon; }; /** - * 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 */ HanCal.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; }; /** * Return true if the given year is a leap year in the Han calendar. - * If the year is given as a year/cycle combination, then the year should be in the - * range [1,60] and the given cycle is the cycle in which the year is located. If + * If the year is given as a year/cycle combination, then the year should be in the + * range [1,60] and the given cycle is the cycle in which the year is located. If * the year is greater than 60, then * it represents the total number of years elapsed in the proleptic calendar since - * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this + * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this * case, the cycle parameter is ignored. - * + * * @param {number} year the year for which the leap year information is being sought * @param {number=} cycle if the given year < 60, this can specify the cycle. If the * cycle is not given, then the year should be given as elapsed years since the beginning @@ -258,14 +258,14 @@ HanCal.prototype.equivalentCycleYear = function(year) { * @return {boolean} true if the given year is a leap year */ HanCal.prototype.isLeapYear = function(year, cycle) { - var calc = HanCal._leapYearCalc(year, cycle); - return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; + var calc = HanCal._leapYearCalc(year, cycle); + return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; }; /** * Return the month of the year that is the leap month. If the given year is * not a leap year, then this method will return -1. - * + * * @param {number} year the year for which the leap year information is being sought * @param {number=} cycle if the given year < 60, this can specify the cycle. If the * cycle is not given, then the year should be given as elapsed years since the beginning @@ -274,50 +274,50 @@ HanCal.prototype.isLeapYear = function(year, cycle) { * if this is not a leap year */ HanCal.prototype.getLeapMonth = function(year, cycle) { - var calc = HanCal._leapYearCalc(year, cycle); - - if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { - return -1; // no leap month - } - - // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. - var month = 0; - var m = HanCal._newMoonOnOrAfter(calc.m1+1); - while (!HanCal._noMajorST(m)) { - month++; - m = HanCal._newMoonOnOrAfter(m+1); - } - - // return the number of the month that is doubled - return month; + var calc = HanCal._leapYearCalc(year, cycle); + + if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { + return -1; // no leap month + } + + // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. + var month = 0; + var m = HanCal._newMoonOnOrAfter(calc.m1+1); + while (!HanCal._noMajorST(m)) { + month++; + m = HanCal._newMoonOnOrAfter(m+1); + } + + // return the number of the month that is doubled + return month; }; /** * Return the date of Chinese New Years in the given calendar year. - * + * * @param {number} year the Chinese year for which the new year information is being sought * @param {number=} cycle if the given year < 60, this can specify the cycle. If the * cycle is not given, then the year should be given as elapsed years since the beginning * of the epoch - * @return {number} the julian day of the beginning of the given year + * @return {number} the julian day of the beginning of the given year */ HanCal.prototype.newYears = function(year, cycle) { - var calc = HanCal._leapYearCalc(year, cycle); - var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); - if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && - (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { - return HanCal._newMoonOnOrAfter(m2+1); - } - return m2; + var calc = HanCal._leapYearCalc(year, cycle); + var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); + if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && + (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { + return HanCal._newMoonOnOrAfter(m2+1); + } + return m2; }; /** * 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 */ HanCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/HanDate.js b/js/lib/HanDate.js index ed2225c355..aab13db5e3 100644 --- a/js/lib/HanDate.js +++ b/js/lib/HanDate.js @@ -1,6 +1,6 @@ /* * HanDate.js - Represent a date in the Han 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 JSUtils = require("./JSUtils.js"); var MathUtils = require("./MathUtils.js"); @@ -34,84 +34,84 @@ var RataDie = require("./RataDie.js"); /** * @class - * - * Construct a new Han date object. The constructor parameters can + * + * Construct a new Han date object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Han date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * + * *
- 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 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 han 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 han date. If the time zone is not + * + *
- locale - locale for this han 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 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 Date * @param {Object=} params parameters that govern the settings and behaviour of this Han date */ var HanDate = function(params) { - params = params || {}; - if (params.locale) { - this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; - } - if (params.timezone) { - this.timezone = params.timezone; - } - + params = params || {}; + if (params.locale) { + this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; + } + if (params.timezone) { + this.timezone = params.timezone; + } + if (!this.timezone) { if (this.locale) { new LocaleInfo(this.locale, { @@ -146,7 +146,7 @@ HanDate.prototype._init = function (params) { loadParams: params && params.loadParams, onLoad: ilib.bind(this, function (cal) { this.cal = cal; - + if (params.year || params.month || params.day || params.hour || params.minute || params.second || params.millisecond || params.cycle || params.cycleYear) { if (typeof(params.cycle) !== 'undefined') { @@ -155,9 +155,9 @@ HanDate.prototype._init = function (params) { * @type number */ this.cycle = parseInt(params.cycle, 10) || 0; - + var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; - + /** * Year in the Han calendar. * @type number @@ -170,63 +170,63 @@ HanDate.prototype._init = function (params) { } else { this.year = this.cycle = 0; } - } - + } + /** * The month number, ranging from 1 to 13 * @type number */ this.month = parseInt(params.month, 10) || 1; - + /** * The day of the month. This ranges from 1 to 30. * @type number */ this.day = parseInt(params.day, 10) || 1; - + /** * The hour of the day. This can be a number from 0 to 23, as times are * stored unambiguously in the 24-hour clock. * @type number */ this.hour = parseInt(params.hour, 10) || 0; - + /** * The minute of the hours. Ranges from 0 to 59. * @type number */ this.minute = parseInt(params.minute, 10) || 0; - + /** * The second of the minute. Ranges from 0 to 59. * @type number */ this.second = parseInt(params.second, 10) || 0; - + /** * The millisecond of the second. Ranges from 0 to 999. * @type number */ this.millisecond = parseInt(params.millisecond, 10) || 0; - + // derived properties - + /** * Year in the cycle of the Han calendar * @type number */ - this.cycleYear = MathUtils.amod(this.year, 60); + this.cycleYear = MathUtils.amod(this.year, 60); /** * The day of the year. Ranges from 1 to 384. * @type number */ this.dayOfYear = parseInt(params.dayOfYear, 10); - + if (typeof(params.dst) === 'boolean') { this.dst = params.dst; } - + this.newRd({ cal: this.cal, cycle: this.cycle, @@ -242,7 +242,7 @@ HanDate.prototype._init = function (params) { callback: ilib.bind(this, function (rd) { if (rd) { this.rd = rd; - + // add the time zone offset to the rd to convert to UTC new TimeZone({ id: this.timezone, @@ -250,13 +250,13 @@ HanDate.prototype._init = function (params) { loadParams: params.loadParams, onLoad: ilib.bind(this, function(tz) { this.tz = tz; - // 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) { // this newRd can be called synchronously because we already called - // it asynchronously above, so all of the astro data should + // it asynchronously above, so all of the astro data should // already be loaded. this.rd = this.newRd({ cal: this.cal, @@ -269,7 +269,7 @@ HanDate.prototype._init = function (params) { this.priorLeapMonth = this.rd.priorLeapMonth; this.leapYear = this.rd.leapYear; } - + this._init2(params); }) }); @@ -322,43 +322,43 @@ HanDate.prototype._init2 = function (params) { * @returns {RataDie} the new RD instance for the given params */ HanDate.prototype.newRd = function (params) { - return new HanRataDie(params); + return new HanRataDie(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 */ HanDate.prototype._calcYear = function(rd) { - var gregdate = new GregorianDate({ - rd: rd, - timezone: this.timezone - }); - var hanyear = gregdate.year + 2697; - var newYears = this.cal.newYears(hanyear); - return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); + var gregdate = new GregorianDate({ + rd: rd, + timezone: this.timezone + }); + var hanyear = gregdate.year + 2697; + var newYears = this.cal.newYears(hanyear); + return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); }; -/** - * @private +/** + * @private * Calculate the leap year and months from the RD. */ HanDate.prototype._calcLeap = function() { - var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; - - var calc = HanCal._leapYearCalc(this.year); - var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); - this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; - - var newYears = (this.leapYear && - (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? - HanCal._newMoonOnOrAfter(m2+1) : m2; - - var m = HanCal._newMoonBefore(jd + 1); - this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); - this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); + var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; + + var calc = HanCal._leapYearCalc(this.year); + var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); + this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; + + var newYears = (this.leapYear && + (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? + HanCal._newMoonOnOrAfter(m2+1) : m2; + + var m = HanCal._newMoonBefore(jd + 1); + this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); + this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); }; /** @@ -366,179 +366,179 @@ HanDate.prototype._calcLeap = function() { * Calculate date components for the given RD date. */ HanDate.prototype._calcDateComponents = function () { - var remainder, - jd = this.rd.getRataDie() + RataDie.gregorianEpoch; - - // console.log("HanDate._calcDateComponents: calculating for jd " + jd); - - if (typeof(this.offset) === "undefined") { - // now offset the jd 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) { - jd += this.offset; - } - - // use the Gregorian calendar objects as a convenient way to short-cut some - // of the date calculations - - var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); - this.year = gregyear + 2697; - var calc = HanCal._leapYearCalc(this.year); - var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); - this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; - var newYears = (this.leapYear && - (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? - HanCal._newMoonOnOrAfter(m2+1) : m2; - - // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If - // so, then the Han year is actually the previous one - if (jd < newYears) { - this.year--; - calc = HanCal._leapYearCalc(this.year); - m2 = HanCal._newMoonOnOrAfter(calc.m1+1); - this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; - newYears = (this.leapYear && - (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? - HanCal._newMoonOnOrAfter(m2+1) : m2; - } - // month is elapsed month, not the month number + leap month boolean - var m = HanCal._newMoonBefore(jd + 1); - this.month = Math.round((m - calc.m1) / 29.530588853000001); - - this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); - this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); - - this.cycle = Math.floor((this.year - 1) / 60); - this.cycleYear = MathUtils.amod(this.year, 60); - this.day = Astro._floorToJD(jd) - m + 1; - - /* - console.log("HanDate._calcDateComponents: year is " + this.year); - console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); - console.log("HanDate._calcDateComponents: cycle is " + this.cycle); - console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); - console.log("HanDate._calcDateComponents: month is " + this.month); - console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); - console.log("HanDate._calcDateComponents: day is " + this.day); - */ - - // floor to the start of the julian day - remainder = jd - Astro._floorToJD(jd); - - // console.log("HanDate._calcDateComponents: time 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, + jd = this.rd.getRataDie() + RataDie.gregorianEpoch; + + // console.log("HanDate._calcDateComponents: calculating for jd " + jd); + + if (typeof(this.offset) === "undefined") { + // now offset the jd 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) { + jd += this.offset; + } + + // use the Gregorian calendar objects as a convenient way to short-cut some + // of the date calculations + + var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); + this.year = gregyear + 2697; + var calc = HanCal._leapYearCalc(this.year); + var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); + this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; + var newYears = (this.leapYear && + (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? + HanCal._newMoonOnOrAfter(m2+1) : m2; + + // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If + // so, then the Han year is actually the previous one + if (jd < newYears) { + this.year--; + calc = HanCal._leapYearCalc(this.year); + m2 = HanCal._newMoonOnOrAfter(calc.m1+1); + this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; + newYears = (this.leapYear && + (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? + HanCal._newMoonOnOrAfter(m2+1) : m2; + } + // month is elapsed month, not the month number + leap month boolean + var m = HanCal._newMoonBefore(jd + 1); + this.month = Math.round((m - calc.m1) / 29.530588853000001); + + this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); + this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); + + this.cycle = Math.floor((this.year - 1) / 60); + this.cycleYear = MathUtils.amod(this.year, 60); + this.day = Astro._floorToJD(jd) - m + 1; + + /* + console.log("HanDate._calcDateComponents: year is " + this.year); + console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); + console.log("HanDate._calcDateComponents: cycle is " + this.cycle); + console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); + console.log("HanDate._calcDateComponents: month is " + this.month); + console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); + console.log("HanDate._calcDateComponents: day is " + this.day); + */ + + // floor to the start of the julian day + remainder = jd - Astro._floorToJD(jd); + + // console.log("HanDate._calcDateComponents: time 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 year within the Chinese cycle of this date. Cycles are 60 - * years long, and the value returned from this method is the number of the year - * within this cycle. The year returned from getYear() is the total elapsed - * years since the beginning of the Chinese epoch and does not include - * the cycles. - * + * Return the year within the Chinese cycle of this date. Cycles are 60 + * years long, and the value returned from this method is the number of the year + * within this cycle. The year returned from getYear() is the total elapsed + * years since the beginning of the Chinese epoch and does not include + * the cycles. + * * @return {number} the year within the current Chinese cycle */ HanDate.prototype.getCycleYears = function() { - return this.cycleYear; + return this.cycleYear; }; /** * Return the Chinese cycle number of this date. Cycles are 60 years long, - * and the value returned from getCycleYear() is the number of the year - * within this cycle. The year returned from getYear() is the total elapsed - * years since the beginning of the Chinese epoch and does not include - * the cycles. - * + * and the value returned from getCycleYear() is the number of the year + * within this cycle. The year returned from getYear() is the total elapsed + * years since the beginning of the Chinese epoch and does not include + * the cycles. + * * @return {number} the current Chinese cycle */ HanDate.prototype.getCycles = function() { - return this.cycle; + return this.cycle; }; /** - * Return whether the year of this date is a leap year in the Chinese Han - * calendar. - * - * @return {boolean} true if the year of this date is a leap year in the - * Chinese Han calendar. + * Return whether the year of this date is a leap year in the Chinese Han + * calendar. + * + * @return {boolean} true if the year of this date is a leap year in the + * Chinese Han calendar. */ HanDate.prototype.isLeapYear = function() { - return this.leapYear; + return this.leapYear; }; /** - * Return whether the month of this date is a leap month in the Chinese Han + * Return whether the month of this date is a leap month in the Chinese Han * calendar. - * - * @return {boolean} true if the month of this date is a leap month in the + * + * @return {boolean} true if the month of this date is a leap month in the * Chinese Han calendar. */ HanDate.prototype.isLeapMonth = function() { - return this.leapMonth; + return this.leapMonth; }; /** * 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 */ HanDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd, 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 */ HanDate.prototype.getDayOfYear = function() { - var newYears = this.cal.newYears(this.year); - var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); - return priorNewMoon - newYears + this.day; + var newYears = this.cal.newYears(this.year); + var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); + return priorNewMoon - newYears + this.day; }; /** - * Return the era for this date as a number. The value for the era for Han - * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno - * persico or AP). - * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, + * Return the era for this date as a number. The value for the era for Han + * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno + * persico or AP). + * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han 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 */ HanDate.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 */ HanDate.prototype.getCalendar = function() { - return "han"; + return "han"; }; // register with the factory method diff --git a/js/lib/HanRataDie.js b/js/lib/HanRataDie.js index 53acac9e50..8436b97d82 100644 --- a/js/lib/HanRataDie.js +++ b/js/lib/HanRataDie.js @@ -1,6 +1,6 @@ /* * HanDate.js - Represent a date in the Han algorithmic calendar - * + * * Copyright © 2014-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,59 +17,59 @@ * limitations under the License. */ -var ilib = require("./ilib.js"); +var ilib = require("../index"); var MathUtils = require("./MathUtils.js"); var HanCal = require("./HanCal.js"); var RataDie = require("./RataDie.js"); /** - * Construct a new Han RD date number object. The constructor parameters can + * Construct a new Han RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Han date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * + * *
- 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 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 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 * @class * @constructor @@ -77,26 +77,26 @@ var RataDie = require("./RataDie.js"); * @param {Object=} params parameters that govern the settings and behaviour of this Han RD date */ var HanRataDie = function(params) { - this.rd = NaN; - if (params && params.cal) { - this.cal = params.cal; - RataDie.call(this, params); - if (params && typeof(params.callback) === 'function') { - params.callback(this); - } - } else { - new HanCal({ - sync: params && params.sync, - loadParams: params && params.loadParams, - onLoad: ilib.bind(this, function(c) { - this.cal = c; - RataDie.call(this, params); - if (params && typeof(params.callback) === 'function') { - params.callback(this); - } - }) - }); - } + this.rd = NaN; + if (params && params.cal) { + this.cal = params.cal; + RataDie.call(this, params); + if (params && typeof(params.callback) === 'function') { + params.callback(this); + } + } else { + new HanCal({ + sync: params && params.sync, + loadParams: params && params.loadParams, + onLoad: ilib.bind(this, function(c) { + this.cal = c; + RataDie.call(this, params); + if (params && typeof(params.callback) === 'function') { + params.callback(this); + } + }) + }); + } }; HanRataDie.prototype = new RataDie(); @@ -119,50 +119,50 @@ HanRataDie.epoch = 758325.5; * @param {Object} date the date components to calculate the RD from */ HanRataDie.prototype._setDateComponents = function(date) { - var calc = HanCal._leapYearCalc(date.year, date.cycle); - var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); - var newYears; - this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); - if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { - newYears = HanCal._newMoonOnOrAfter(m2+1); - } else { - newYears = m2; - } + var calc = HanCal._leapYearCalc(date.year, date.cycle); + var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); + var newYears; + this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); + if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { + newYears = HanCal._newMoonOnOrAfter(m2+1); + } else { + newYears = m2; + } + + var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day + this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); + this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); + + var rdtime = (date.hour * 3600000 + + date.minute * 60000 + + date.second * 1000 + + date.millisecond) / + 86400000; - var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day - this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); - this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); + /* + console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); + console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); + console.log("getRataDie: isLeapYear is " + this.leapYear); + console.log("getRataDie: priorNewMoon is " + priorNewMoon); + console.log("getRataDie: day in month is " + date.day); + console.log("getRataDie: rdtime is " + rdtime); + console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); + */ - var rdtime = (date.hour * 3600000 + - date.minute * 60000 + - date.second * 1000 + - date.millisecond) / - 86400000; - - /* - console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); - console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); - console.log("getRataDie: isLeapYear is " + this.leapYear); - console.log("getRataDie: priorNewMoon is " + priorNewMoon); - console.log("getRataDie: day in month is " + date.day); - console.log("getRataDie: rdtime is " + rdtime); - console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); - */ - - this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; + this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; }; /** - * 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 */ HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); }; /** @@ -170,13 +170,13 @@ HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { * @static * @param {number} jd1 first julian day * @param {number} jd2 second julian day - * @returns {boolean} true if there is a leap month earlier in the same year - * as the given months + * @returns {boolean} true if there is a leap month earlier in the same year + * as the given months */ HanRataDie._priorLeapMonth = function(jd1, jd2) { - return jd2 >= jd1 && - (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || - HanCal._noMajorST(jd2)); + return jd2 >= jd1 && + (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || + HanCal._noMajorST(jd2)); }; diff --git a/js/lib/HebrewCal.js b/js/lib/HebrewCal.js index 9ade37ab8c..3e6accf474 100644 --- a/js/lib/HebrewCal.js +++ b/js/lib/HebrewCal.js @@ -1,6 +1,6 @@ /* * HebrewCal.js - Represent a Hebrew calendar object. - * + * * Copyright © 2012-2015,2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,24 +23,24 @@ var Calendar = require("./Calendar.js"); /** * @class * Construct a new Hebrew calendar object. This class encodes information about - * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew - * calendar where the dates are calculated by arithmetic rules. This differs from - * the religious Hebrew calendar which is used to mark the beginning of particular - * holidays. The religious calendar depends on the first sighting of the new - * crescent moon to determine the first day of the new month. Because humans and - * weather are both involved, the actual time of sighting varies, so it is not - * really possible to precalculate the religious calendar. Certain groups, such + * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew + * calendar where the dates are calculated by arithmetic rules. This differs from + * the religious Hebrew calendar which is used to mark the beginning of particular + * holidays. The religious calendar depends on the first sighting of the new + * crescent moon to determine the first day of the new month. Because humans and + * weather are both involved, the actual time of sighting varies, so it is not + * really possible to precalculate the religious calendar. Certain groups, such * as the Hebrew Society of North America, decreed in in 2007 that they will use - * a calendar based on calculations rather than observations to determine the + * a calendar based on calculations rather than observations to determine the * beginning of lunar months, and therefore the dates of holidays.
- * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var HebrewCal = function(options) { - this.type = "hebrew"; - + this.type = "hebrew"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -55,22 +55,22 @@ var HebrewCal = function(options) { * given year starts */ HebrewCal.elapsedDays = function(year) { - var months = Math.floor(((235*year) - 234)/19); - var parts = 204 + 793 * MathUtils.mod(months, 1080); - var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + - Math.floor(parts/1080); - var days = 29 * months + Math.floor(hours/24); - return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; + var months = Math.floor(((235*year) - 234)/19); + var parts = 204 + 793 * MathUtils.mod(months, 1080); + var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + + Math.floor(parts/1080); + var days = 29 * months + Math.floor(hours/24); + return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; }; /** - * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew - * calendar will be corrected for the given year. Corrections are caused because New - * Year's is not allowed to start on certain days of the week. To deal with - * it, the start of the new year is corrected for the next year by adding a + * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew + * calendar will be corrected for the given year. Corrections are caused because New + * Year's is not allowed to start on certain days of the week. To deal with + * it, the start of the new year is corrected for the next year by adding a * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current * year to make them 30 days long instead of 29. - * + * * @private * @param {number} year the year for which the correction is sought * @param {number} elapsed number of days elapsed up to this year @@ -78,11 +78,11 @@ HebrewCal.elapsedDays = function(year) { * Rosh HaShanah does not fall on undesirable days of the week */ HebrewCal.newYearsCorrection = function(year, elapsed) { - var lastYear = HebrewCal.elapsedDays(year-1), - thisYear = elapsed, - nextYear = HebrewCal.elapsedDays(year+1); - - return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); + var lastYear = HebrewCal.elapsedDays(year-1), + thisYear = elapsed, + nextYear = HebrewCal.elapsedDays(year+1); + + return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); }; /** @@ -92,9 +92,9 @@ HebrewCal.newYearsCorrection = function(year, elapsed) { * @return {number} the rata die date of the new year */ HebrewCal.newYear = function(year) { - var elapsed = HebrewCal.elapsedDays(year); - - return elapsed + HebrewCal.newYearsCorrection(year, elapsed); + var elapsed = HebrewCal.elapsedDays(year); + + return elapsed + HebrewCal.newYearsCorrection(year, elapsed); }; /** @@ -103,83 +103,83 @@ HebrewCal.newYear = function(year) { * fall on particular days of the week. Days are added to the months of Heshvan * and/or Kislev in the previous year in order to prevent the current year's New * Year from being on Sunday, Wednesday, or Friday. - * + * * @param {number} year the year for which the length is sought * @return {number} number of days in the given year */ HebrewCal.daysInYear = function(year) { - return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); + return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); }; /** * Return true if the given year contains a long month of Heshvan. That is, * it is 30 days instead of 29. - * + * * @private * @param {number} year the year in which that month is questioned * @return {boolean} true if the given year contains a long month of Heshvan */ HebrewCal.longHeshvan = function(year) { - return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; + return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; }; /** * Return true if the given year contains a long month of Kislev. That is, * it is 30 days instead of 29. - * + * * @private * @param {number} year the year in which that month is questioned * @return {boolean} true if the given year contains a short month of Kislev */ HebrewCal.longKislev = function(year) { - return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; + return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; }; /** * Return the date of the last day of the month for the given year. The date of - * the last day of the month is variable because a number of months gain an extra - * day in leap years, and it is variable which months gain a day for each leap + * the last day of the month is variable because a number of months gain an extra + * day in leap years, and it is variable which months gain a day for each leap * year and which do not. - * + * * @param {number} month the month for which the number of days is sought * @param {number} year the year in which that month is * @return {number} the number of days in the given month and year */ HebrewCal.prototype.lastDayOfMonth = function(month, year) { - switch (month) { - case 2: - case 4: - case 6: - case 10: - return 29; - case 13: - return this.isLeapYear(year) ? 29 : 0; - case 8: - return HebrewCal.longHeshvan(year) ? 30 : 29; - case 9: - return HebrewCal.longKislev(year) ? 30 : 29; - case 12: - case 1: - case 3: - case 5: - case 7: - case 11: - return 30; - default: - return 0; - } + switch (month) { + case 2: + case 4: + case 6: + case 10: + return 29; + case 13: + return this.isLeapYear(year) ? 29 : 0; + case 8: + return HebrewCal.longHeshvan(year) ? 30 : 29; + case 9: + return HebrewCal.longKislev(year) ? 30 : 29; + case 12: + case 1: + case 3: + case 5: + case 7: + case 11: + return 30; + default: + return 0; + } }; /** * Return the number of months in the given year. The number of months in a year varies - * for luni-solar calendars because in some years, an extra month is needed to extend the + * for 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 */ HebrewCal.prototype.getNumMonths = function(year) { - return this.isLeapYear(year) ? 13 : 12; + return this.isLeapYear(year) ? 13 : 12; }; /** @@ -192,10 +192,10 @@ HebrewCal.prototype.getNumMonths = function(year) { * 0 for an invalid month in the year */ HebrewCal.prototype.getMonLength = function(month, year) { - if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { - return 0; - } - return this.lastDayOfMonth(month, year); + if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { + return 0; + } + return this.lastDayOfMonth(month, year); }; /** @@ -205,17 +205,17 @@ HebrewCal.prototype.getMonLength = function(month, year) { * @returns {boolean} true if the given year is a leap year */ HebrewCal.prototype.isLeapYear = function(year) { - var y = (typeof(year) == 'number') ? year : year.year; - return (MathUtils.mod(1 + 7 * y, 19) < 7); + var y = (typeof(year) == 'number') ? year : year.year; + return (MathUtils.mod(1 + 7 * y, 19) < 7); }; /** * Return the type of this calendar. - * - * @returns {string} the name of the type of this calendar + * + * @returns {string} the name of the type of this calendar */ HebrewCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/HebrewDate.js b/js/lib/HebrewDate.js index 985d60a28b..4f84bcc9ea 100644 --- a/js/lib/HebrewDate.js +++ b/js/lib/HebrewDate.js @@ -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"); @@ -236,19 +236,19 @@ HebrewDate.prototype._init2 = function (params) { */ HebrewDate.cumMonthLengthsReverse = [ // [days, monthnumber], - [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ - [30, 8], /* Heshvan */ - [59, 9], /* Kislev */ - [88, 10], /* Teveth */ - [117, 11], /* Shevat */ - [147, 12], /* Adar I */ - [176, 1], /* Nisan */ - [206, 2], /* Iyyar */ - [235, 3], /* Sivan */ - [265, 4], /* Tammuz */ - [294, 5], /* Av */ - [324, 6], /* Elul */ - [354, 7] /* end of year sentinel value */ + [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ + [30, 8], /* Heshvan */ + [59, 9], /* Kislev */ + [88, 10], /* Teveth */ + [117, 11], /* Shevat */ + [147, 12], /* Adar I */ + [176, 1], /* Nisan */ + [206, 2], /* Iyyar */ + [235, 3], /* Sivan */ + [265, 4], /* Tammuz */ + [294, 5], /* Av */ + [324, 6], /* Elul */ + [354, 7] /* end of year sentinel value */ ]; /** @@ -261,20 +261,20 @@ HebrewDate.cumMonthLengthsReverse = [ */ HebrewDate.cumMonthLengthsLeapReverse = [ // [days, monthnumber], - [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ - [30, 8], /* Heshvan */ - [59, 9], /* Kislev */ - [88, 10], /* Teveth */ - [117, 11], /* Shevat */ - [147, 12], /* Adar I */ - [177, 13], /* Adar II */ - [206, 1], /* Nisan */ - [236, 2], /* Iyyar */ - [265, 3], /* Sivan */ - [295, 4], /* Tammuz */ - [324, 5], /* Av */ - [354, 6], /* Elul */ - [384, 7] /* end of year sentinel value */ + [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ + [30, 8], /* Heshvan */ + [59, 9], /* Kislev */ + [88, 10], /* Teveth */ + [117, 11], /* Shevat */ + [147, 12], /* Adar I */ + [177, 13], /* Adar II */ + [206, 1], /* Nisan */ + [236, 2], /* Iyyar */ + [265, 3], /* Sivan */ + [295, 4], /* Tammuz */ + [324, 5], /* Av */ + [354, 6], /* Elul */ + [384, 7] /* end of year sentinel value */ ]; /** @@ -294,7 +294,7 @@ HebrewDate.GregorianDiff = 1373060.25; * @returns {RataDie} the new RD instance for the given params */ HebrewDate.prototype.newRd = function (params) { - return new HebrewRataDie(params); + return new HebrewRataDie(params); }; /** @@ -304,22 +304,22 @@ HebrewDate.prototype.newRd = function (params) { * @returns {number} the year for the RD */ HebrewDate.prototype._calcYear = function(rd) { - var year, approximation, nextNewYear; - - // divide by the average number of days per year in the Hebrew calendar - // to approximate the year, then tweak it to get the real year - approximation = Math.floor(rd / 365.246822206) + 1; - - // console.log("HebrewDate._calcYear: approx is " + approximation); - - // search forward from approximation-1 for the year that actually contains this rd - year = approximation; - nextNewYear = HebrewCal.newYear(year); - while (rd >= nextNewYear) { - year++; - nextNewYear = HebrewCal.newYear(year); - } - return year - 1; + var year, approximation, nextNewYear; + + // divide by the average number of days per year in the Hebrew calendar + // to approximate the year, then tweak it to get the real year + approximation = Math.floor(rd / 365.246822206) + 1; + + // console.log("HebrewDate._calcYear: approx is " + approximation); + + // search forward from approximation-1 for the year that actually contains this rd + year = approximation; + nextNewYear = HebrewCal.newYear(year); + while (rd >= nextNewYear) { + year++; + nextNewYear = HebrewCal.newYear(year); + } + return year - 1; }; /** @@ -327,92 +327,92 @@ HebrewDate.prototype._calcYear = function(rd) { * @protected */ HebrewDate.prototype._calcDateComponents = function () { - var remainder, - i, - table, - target, - rd = this.rd.getRataDie(); - - // console.log("HebrewDate.calcComponents: calculating for rd " + rd); - - if (typeof(this.offset) === "undefined") { - this.year = this._calcYear(rd); - - // 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("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); - - remainder = rd - HebrewCal.newYear(this.year); - // console.log("HebrewDate.calcComponents: remainder is " + remainder); - - // take out new years corrections so we get the right month when we look it up in the table - if (remainder >= 59) { - if (remainder >= 88) { - if (HebrewCal.longKislev(this.year)) { - remainder--; - } - } - if (HebrewCal.longHeshvan(this.year)) { - remainder--; - } - } - - // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); - - table = this.cal.isLeapYear(this.year) ? - HebrewDate.cumMonthLengthsLeapReverse : - HebrewDate.cumMonthLengthsReverse; - - i = 0; - target = Math.floor(remainder); - while (i+1 < table.length && target >= table[i+1][0]) { - i++; - } - - this.month = table[i][1]; - // console.log("HebrewDate.calcComponents: remainder is " + remainder); - remainder -= table[i][0]; - - // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); - - this.day = Math.floor(remainder); - remainder -= this.day; - this.day++; // days are 1-based - - // console.log("HebrewDate.calcComponents: 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; - - // the hours from 0 to 6 are actually 18:00 to midnight of the previous - // gregorian day, so we have to adjust for that - if (this.hour >= 6) { - this.hour -= 6; - } else { - this.hour += 18; - } - - this.minute = Math.floor(remainder/60000); - remainder -= this.minute * 60000; - - this.second = Math.floor(remainder/1000); - remainder -= this.second * 1000; - - this.millisecond = Math.floor(remainder); + var remainder, + i, + table, + target, + rd = this.rd.getRataDie(); + + // console.log("HebrewDate.calcComponents: calculating for rd " + rd); + + if (typeof(this.offset) === "undefined") { + this.year = this._calcYear(rd); + + // 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("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); + + remainder = rd - HebrewCal.newYear(this.year); + // console.log("HebrewDate.calcComponents: remainder is " + remainder); + + // take out new years corrections so we get the right month when we look it up in the table + if (remainder >= 59) { + if (remainder >= 88) { + if (HebrewCal.longKislev(this.year)) { + remainder--; + } + } + if (HebrewCal.longHeshvan(this.year)) { + remainder--; + } + } + + // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); + + table = this.cal.isLeapYear(this.year) ? + HebrewDate.cumMonthLengthsLeapReverse : + HebrewDate.cumMonthLengthsReverse; + + i = 0; + target = Math.floor(remainder); + while (i+1 < table.length && target >= table[i+1][0]) { + i++; + } + + this.month = table[i][1]; + // console.log("HebrewDate.calcComponents: remainder is " + remainder); + remainder -= table[i][0]; + + // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); + + this.day = Math.floor(remainder); + remainder -= this.day; + this.day++; // days are 1-based + + // console.log("HebrewDate.calcComponents: 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; + + // the hours from 0 to 6 are actually 18:00 to midnight of the previous + // gregorian day, so we have to adjust for that + if (this.hour >= 6) { + this.hour -= 6; + } else { + this.hour += 18; + } + + this.minute = Math.floor(remainder/60000); + remainder -= this.minute * 60000; + + this.second = Math.floor(remainder/1000); + remainder -= this.second * 1000; + + this.millisecond = Math.floor(remainder); }; /** @@ -422,8 +422,8 @@ HebrewDate.prototype._calcDateComponents = function () { * @return {number} the day of the week */ HebrewDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd+1, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd+1, 7); }; /** @@ -434,12 +434,12 @@ HebrewDate.prototype.getDayOfWeek = function() { * @return {number} the halaqim parts of the current hour */ HebrewDate.prototype.getHalaqim = function() { - if (this.parts < 0) { - // convert to ms first, then to parts - var h = this.minute * 60000 + this.second * 1000 + this.millisecond; - this.parts = (h * 0.0003); - } - return this.parts; + if (this.parts < 0) { + // convert to ms first, then to parts + var h = this.minute * 60000 + this.second * 1000 + this.millisecond; + this.parts = (h * 0.0003); + } + return this.parts; }; /** @@ -448,21 +448,21 @@ HebrewDate.prototype.getHalaqim = function() { * @return the rd of the first Sunday of the ISO year */ HebrewDate.prototype.firstSunday = function (year) { - var tishri1 = this.newRd({ - year: year, - month: 7, - day: 1, - hour: 18, - minute: 0, - second: 0, - millisecond: 0, - cal: this.cal - }); - var firstThu = this.newRd({ - rd: tishri1.onOrAfter(4), - cal: this.cal - }); - return firstThu.before(0); + var tishri1 = this.newRd({ + year: year, + month: 7, + day: 1, + hour: 18, + minute: 0, + second: 0, + millisecond: 0, + cal: this.cal + }); + var firstThu = this.newRd({ + rd: tishri1.onOrAfter(4), + cal: this.cal + }); + return firstThu.before(0); }; /** @@ -472,18 +472,18 @@ HebrewDate.prototype.firstSunday = function (year) { * @return {number} the ordinal day of the year */ HebrewDate.prototype.getDayOfYear = function() { - var table = this.cal.isLeapYear(this.year) ? - HebrewRataDie.cumMonthLengthsLeap : - HebrewRataDie.cumMonthLengths; - var days = table[this.month-1]; - if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { - days++; - } - if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { - days++; - } - - return days + this.day; + var table = this.cal.isLeapYear(this.year) ? + HebrewRataDie.cumMonthLengthsLeap : + HebrewRataDie.cumMonthLengths; + var days = table[this.month-1]; + if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { + days++; + } + if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { + days++; + } + + return days + this.day; }; /** @@ -501,26 +501,26 @@ HebrewDate.prototype.getDayOfYear = function() { * @return {number} the ordinal number of the week within the current month */ HebrewDate.prototype.getWeekOfMonth = function(locale) { - var li = new LocaleInfo(locale), - first = this.newRd({ - year: this.year, - month: this.month, - day: 1, - hour: 18, - minute: 0, - second: 0, - millisecond: 0 - }), - rd = this.rd.getRataDie(), - weekStart = first.onOrAfter(li.getFirstDayOfWeek()); - - if (weekStart - first.getRataDie() > 3) { - // if the first week has 4 or more days in it of the current month, then consider - // that week 1. Otherwise, it is week 0. To make it week 1, move the week start - // one week earlier. - weekStart -= 7; - } - return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; + var li = new LocaleInfo(locale), + first = this.newRd({ + year: this.year, + month: this.month, + day: 1, + hour: 18, + minute: 0, + second: 0, + millisecond: 0 + }), + rd = this.rd.getRataDie(), + weekStart = first.onOrAfter(li.getFirstDayOfWeek()); + + if (weekStart - first.getRataDie() > 3) { + // if the first week has 4 or more days in it of the current month, then consider + // that week 1. Otherwise, it is week 0. To make it week 1, move the week start + // one week earlier. + weekStart -= 7; + } + return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; }; /** @@ -533,7 +533,7 @@ HebrewDate.prototype.getWeekOfMonth = function(locale) { * Hebrew era */ HebrewDate.prototype.getEra = function() { - return (this.year < 1) ? -1 : 1; + return (this.year < 1) ? -1 : 1; }; /** @@ -542,7 +542,7 @@ HebrewDate.prototype.getEra = function() { * @return {string} a string giving the name of the calendar */ HebrewDate.prototype.getCalendar = function() { - return "hebrew"; + return "hebrew"; }; // register with the factory method diff --git a/js/lib/HebrewRataDie.js b/js/lib/HebrewRataDie.js index b3a5f3d5c6..b8234d24f1 100644 --- a/js/lib/HebrewRataDie.js +++ b/js/lib/HebrewRataDie.js @@ -1,6 +1,6 @@ /* * HebrewRataDie.js - Represent an RD date in the Hebrew calendar - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,59 +23,59 @@ var RataDie = require("./RataDie.js"); /** * @class - * Construct a new Hebrew RD date number object. The constructor parameters can + * Construct a new Hebrew RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Hebrew date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * - *
- 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. - * + * + *
- 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. + * *
- 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 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 Hebrew RD date */ var HebrewRataDie = function(params) { - this.cal = params && params.cal || new HebrewCal(); - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new HebrewCal(); + this.rd = NaN; + RataDie.call(this, params); }; HebrewRataDie.prototype = new RataDie(); @@ -83,7 +83,7 @@ HebrewRataDie.prototype.parent = RataDie; HebrewRataDie.prototype.constructor = HebrewRataDie; /** - * The difference between a zero Julian day and the first day of the Hebrew + * The difference between a zero Julian day and the first day of the Hebrew * calendar: sunset on Monday, Tishri 1, 1 = September 7, 3760 BC Gregorian = JD 347997.25 * @private * @type number @@ -97,129 +97,129 @@ HebrewRataDie.prototype.epoch = 347997.25; * @type Array.
*/ HebrewRataDie.cumMonthLengths = [ - 176, /* Nisan */ - 206, /* Iyyar */ - 235, /* Sivan */ - 265, /* Tammuz */ - 294, /* Av */ - 324, /* Elul */ - 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ - 30, /* Heshvan */ - 59, /* Kislev */ - 88, /* Teveth */ - 117, /* Shevat */ - 147 /* Adar I */ + 176, /* Nisan */ + 206, /* Iyyar */ + 235, /* Sivan */ + 265, /* Tammuz */ + 294, /* Av */ + 324, /* Elul */ + 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ + 30, /* Heshvan */ + 59, /* Kislev */ + 88, /* Teveth */ + 117, /* Shevat */ + 147 /* Adar I */ ]; /** - * the cumulative lengths of each month for a leap year, without new years corrections + * the cumulative lengths of each month for a leap year, without new years corrections * @private * @const * @type Array. */ HebrewRataDie.cumMonthLengthsLeap = [ - 206, /* Nisan */ - 236, /* Iyyar */ - 265, /* Sivan */ - 295, /* Tammuz */ - 324, /* Av */ - 354, /* Elul */ - 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ - 30, /* Heshvan */ - 59, /* Kislev */ - 88, /* Teveth */ - 117, /* Shevat */ - 147, /* Adar I */ - 177 /* Adar II */ + 206, /* Nisan */ + 236, /* Iyyar */ + 265, /* Sivan */ + 295, /* Tammuz */ + 324, /* Av */ + 354, /* Elul */ + 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ + 30, /* Heshvan */ + 59, /* Kislev */ + 88, /* Teveth */ + 117, /* Shevat */ + 147, /* Adar I */ + 177 /* Adar II */ ]; /** * Calculate the Rata Die (fixed day) number of the given date from the * date components. - * + * * @private * @param {Object} date the date components to calculate the RD from */ HebrewRataDie.prototype._setDateComponents = function(date) { - var elapsed = HebrewCal.elapsedDays(date.year); - var days = elapsed + - HebrewCal.newYearsCorrection(date.year, elapsed) + - date.day - 1; - var sum = 0, table; - - //console.log("getRataDie: converting " + JSON.stringify(date)); - //console.log("getRataDie: days is " + days); - //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); - - table = this.cal.isLeapYear(date.year) ? - HebrewRataDie.cumMonthLengthsLeap : - HebrewRataDie.cumMonthLengths; - sum = table[date.month-1]; - - // gets cumulative without correction, so now add in the correction - if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { - sum++; - } - if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { - sum++; - } - // console.log("getRataDie: cum days is now " + sum); - - days += sum; - - // the date starts at sunset, which we take as 18:00, so the hours from - // midnight to 18:00 are on the current Gregorian day, and the hours from - // 18:00 to midnight are on the previous Gregorian day. So to calculate the - // number of hours into the current day that this time represents, we have - // to count from 18:00 to midnight first, and add in 6 hours if the time is - // less than 18:00 - var minute, second, millisecond; - - if (typeof(date.parts) !== 'undefined') { - // The parts (halaqim) of the hour. This can be a number from 0 to 1079. - var parts = parseInt(date.parts, 10); - var seconds = parseInt(parts, 10) * 3.333333333333; - minute = Math.floor(seconds / 60); - seconds -= minute * 60; - second = Math.floor(seconds); - millisecond = (seconds - second); - } else { - minute = parseInt(date.minute, 10) || 0; - second = parseInt(date.second, 10) || 0; - millisecond = parseInt(date.millisecond, 10) || 0; - } - - var time; - if (date.hour >= 18) { - time = ((date.hour - 18 || 0) * 3600000 + - (minute || 0) * 60000 + - (second || 0) * 1000 + - (millisecond || 0)) / - 86400000; - } else { - time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day - ((date.hour || 0) * 3600000 + - (minute || 0) * 60000 + - (second || 0) * 1000 + - (millisecond || 0)) / - 86400000; - } - - //console.log("getRataDie: rd is " + (days + time)); - this.rd = days + time; + var elapsed = HebrewCal.elapsedDays(date.year); + var days = elapsed + + HebrewCal.newYearsCorrection(date.year, elapsed) + + date.day - 1; + var sum = 0, table; + + //console.log("getRataDie: converting " + JSON.stringify(date)); + //console.log("getRataDie: days is " + days); + //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); + + table = this.cal.isLeapYear(date.year) ? + HebrewRataDie.cumMonthLengthsLeap : + HebrewRataDie.cumMonthLengths; + sum = table[date.month-1]; + + // gets cumulative without correction, so now add in the correction + if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { + sum++; + } + if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { + sum++; + } + // console.log("getRataDie: cum days is now " + sum); + + days += sum; + + // the date starts at sunset, which we take as 18:00, so the hours from + // midnight to 18:00 are on the current Gregorian day, and the hours from + // 18:00 to midnight are on the previous Gregorian day. So to calculate the + // number of hours into the current day that this time represents, we have + // to count from 18:00 to midnight first, and add in 6 hours if the time is + // less than 18:00 + var minute, second, millisecond; + + if (typeof(date.parts) !== 'undefined') { + // The parts (halaqim) of the hour. This can be a number from 0 to 1079. + var parts = parseInt(date.parts, 10); + var seconds = parseInt(parts, 10) * 3.333333333333; + minute = Math.floor(seconds / 60); + seconds -= minute * 60; + second = Math.floor(seconds); + millisecond = (seconds - second); + } else { + minute = parseInt(date.minute, 10) || 0; + second = parseInt(date.second, 10) || 0; + millisecond = parseInt(date.millisecond, 10) || 0; + } + + var time; + if (date.hour >= 18) { + time = ((date.hour - 18 || 0) * 3600000 + + (minute || 0) * 60000 + + (second || 0) * 1000 + + (millisecond || 0)) / + 86400000; + } else { + time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day + ((date.hour || 0) * 3600000 + + (minute || 0) * 60000 + + (second || 0) * 1000 + + (millisecond || 0)) / + 86400000; + } + + //console.log("getRataDie: rd is " + (days + time)); + this.rd = days + time; }; - + /** - * 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 */ HebrewRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { - return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); + return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); }; module.exports = HebrewRataDie; diff --git a/js/lib/IDate.js b/js/lib/IDate.js index ebd9ea7165..9b41bd7c77 100644 --- a/js/lib/IDate.js +++ b/js/lib/IDate.js @@ -1,7 +1,7 @@ /* - * IDate.js - Represent a date in any calendar. This class is subclassed for each + * IDate.js - Represent a date in any calendar. This class is subclassed for each * calendar and includes some shared functionality. - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +22,13 @@ var LocaleInfo = require("./LocaleInfo.js"); /** * @class - * Superclass for all the calendar date classes that contains shared + * Superclass for all the calendar date classes that contains shared * functionality. This class is never instantiated on its own. Instead, * you should use the {@link DateFactory} function to manufacture a new * instance of a subclass of IDate. This class is called IDate for "ilib * date" so that it does not conflict with the built-in Javascript Date * class. - * + * * @private * @constructor * @param {Object=} options The date components to initialize this date with @@ -37,352 +37,352 @@ var IDate = function(options) { }; /* place for the subclasses to put their constructors so that the factory method - * can find them. Do this to add your date after it's defined: + * can find them. Do this to add your date after it's defined: * IDate._constructors["mytype"] = IDate.MyTypeConstructor; */ IDate._constructors = {}; IDate.prototype = { - getType: function() { - return "date"; - }, - - /** - * Return the unix time equivalent to this date instance. Unix time is - * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This - * method only returns a valid number for dates between midnight, - * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when - * the unix time runs out. If this instance encodes a date outside of that range, - * this method will return -1. For date types that are not Gregorian, the point - * in time represented by this date object will only give a return value if it - * is in the correct range in the Gregorian calendar as given previously. - * - * @return {number} a number giving the unix time, or -1 if the date is outside the - * valid unix time range - */ - getTime: function() { - return this.rd.getTime(); - }, - - /** - * 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 an 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() { - return this.rd.getTimeExtended(); - }, + getType: function() { + return "date"; + }, + + /** + * Return the unix time equivalent to this date instance. Unix time is + * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This + * method only returns a valid number for dates between midnight, + * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when + * the unix time runs out. If this instance encodes a date outside of that range, + * this method will return -1. For date types that are not Gregorian, the point + * in time represented by this date object will only give a return value if it + * is in the correct range in the Gregorian calendar as given previously. + * + * @return {number} a number giving the unix time, or -1 if the date is outside the + * valid unix time range + */ + getTime: function() { + return this.rd.getTime(); + }, + + /** + * 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 an 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() { + return this.rd.getTimeExtended(); + }, + + /** + * Set the time of this instance according to the given unix time. Unix time is + * the number of milliseconds since midnight on Jan 1, 1970. + * + * @param {number} millis the unix time to set this date to in milliseconds + */ + setTime: function(millis) { + this.rd = this.newRd({ + unixtime: millis, + cal: this.cal + }); + this._calcDateComponents(); + }, + + getDays: function() { + return this.day; + }, + getMonths: function() { + return this.month; + }, + getYears: function() { + return this.year; + }, + getHours: function() { + return this.hour; + }, + getMinutes: function() { + return this.minute; + }, + getSeconds: function() { + return this.second; + }, + getMilliseconds: function() { + return this.millisecond; + }, + getEra: function() { + return (this.year < 1) ? -1 : 1; + }, + + setDays: function(day) { + this.day = parseInt(day, 10) || 1; + this.rd._setDateComponents(this); + }, + setMonths: function(month) { + this.month = parseInt(month, 10) || 1; + this.rd._setDateComponents(this); + }, + setYears: function(year) { + this.year = parseInt(year, 10) || 0; + this.rd._setDateComponents(this); + }, + + setHours: function(hour) { + this.hour = parseInt(hour, 10) || 0; + this.rd._setDateComponents(this); + }, + setMinutes: function(minute) { + this.minute = parseInt(minute, 10) || 0; + this.rd._setDateComponents(this); + }, + setSeconds: function(second) { + this.second = parseInt(second, 10) || 0; + this.rd._setDateComponents(this); + }, + setMilliseconds: function(milli) { + this.millisecond = parseInt(milli, 10) || 0; + this.rd._setDateComponents(this); + }, + + /** + * Return a new date instance in the current calendar 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 + */ + before: function (dow) { + return new this.constructor({ + rd: this.rd.before(dow, this.offset), + timezone: this.timezone + }); + }, + + /** + * Return a new date instance in the current calendar 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 + */ + after: function (dow) { + return new this.constructor({ + rd: this.rd.after(dow, this.offset), + timezone: this.timezone + }); + }, + + /** + * 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 + */ + onOrBefore: function (dow) { + return new this.constructor({ + rd: this.rd.onOrBefore(dow, this.offset), + timezone: this.timezone + }); + }, + + /** + * 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 + */ + onOrAfter: function (dow) { + return new this.constructor({ + rd: this.rd.onOrAfter(dow, this.offset), + timezone: this.timezone + }); + }, + + /** + * Return a Javascript Date object that is equivalent to this date + * object. + * + * @return {Date|undefined} a javascript Date object + */ + getJSDate: function() { + var unix = this.rd.getTimeExtended(); + return isNaN(unix) ? undefined : new Date(unix); + }, + + /** + * Return the Rata Die (fixed day) number of this date. + * + * @protected + * @return {number} the rd date as a number + */ + getRataDie: function() { + return this.rd.getRataDie(); + }, + + /** + * Set the date components of this instance based on the given rd. + * @protected + * @param {number} rd the rata die date to set + */ + setRd: function (rd) { + this.rd = this.newRd({ + rd: rd, + cal: this.cal + }); + this._calcDateComponents(); + }, + + /** + * Return the Julian Day equivalent to this calendar date as a number. + * + * @return {number} the julian date equivalent of this date + */ + getJulianDay: function() { + return this.rd.getJulianDay(); + }, + + /** + * Set the date of this instance using a Julian Day. + * @param {number|JulianDay} date the Julian Day to use to set this date + */ + setJulianDay: function (date) { + this.rd = this.newRd({ + julianday: (typeof(date) === 'object') ? date.getDate() : date, + cal: this.cal + }); + this._calcDateComponents(); + }, + + /** + * Return the time zone associated with this date, or + * undefined if none was specified in the constructor. + * + * @return {string|undefined} the name of the time zone for this date instance + */ + getTimeZone: function() { + return this.timezone || "local"; + }, + + /** + * Set the time zone associated with this date. + * @param {string=} tzName the name of the time zone to set into this date instance, + * or "undefined" to unset the time zone + */ + setTimeZone: function (tzName) { + if (!tzName || tzName === "") { + // same as undefining it + this.timezone = undefined; + this.tz = undefined; + } else if (typeof(tzName) === 'string') { + this.timezone = tzName; + this.tz = undefined; + // assuming the same UTC time, but a new time zone, now we have to + // recalculate what the date components are + this._calcDateComponents(); + } + }, + + /** + * Return the rd number of the first Sunday of the given ISO year. + * @protected + * @param {number} year the year for which the first Sunday is being sought + * @return {number} the rd of the first Sunday of the ISO year + */ + firstSunday: function (year) { + var firstDay = this.newRd({ + year: year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + cal: this.cal + }); + var firstThu = this.newRd({ + rd: firstDay.onOrAfter(4), + cal: this.cal + }); + return firstThu.before(0); + }, + + /** + * Return the ISO 8601 week number in the current year for the current date. The week + * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some + * calendars. + * + * @return {number} the week number for the current date + */ + getWeekOfYear: function() { + var rd = Math.floor(this.rd.getRataDie()); + var year = this._calcYear(rd + this.offset); + var yearStart = this.firstSunday(year); + var nextYear; - /** - * Set the time of this instance according to the given unix time. Unix time is - * the number of milliseconds since midnight on Jan 1, 1970. - * - * @param {number} millis the unix time to set this date to in milliseconds - */ - setTime: function(millis) { - this.rd = this.newRd({ - unixtime: millis, - cal: this.cal - }); - this._calcDateComponents(); - }, - - getDays: function() { - return this.day; - }, - getMonths: function() { - return this.month; - }, - getYears: function() { - return this.year; - }, - getHours: function() { - return this.hour; - }, - getMinutes: function() { - return this.minute; - }, - getSeconds: function() { - return this.second; - }, - getMilliseconds: function() { - return this.millisecond; - }, - getEra: function() { - return (this.year < 1) ? -1 : 1; - }, + // if we have a January date, it may be in this ISO year or the previous year + if (rd < yearStart) { + yearStart = this.firstSunday(year-1); + } else { + // if we have a late December date, it may be in this ISO year, or the next year + nextYear = this.firstSunday(year+1); + if (rd >= nextYear) { + yearStart = nextYear; + } + } - setDays: function(day) { - this.day = parseInt(day, 10) || 1; - this.rd._setDateComponents(this); - }, - setMonths: function(month) { - this.month = parseInt(month, 10) || 1; - this.rd._setDateComponents(this); - }, - setYears: function(year) { - this.year = parseInt(year, 10) || 0; - this.rd._setDateComponents(this); - }, - - setHours: function(hour) { - this.hour = parseInt(hour, 10) || 0; - this.rd._setDateComponents(this); - }, - setMinutes: function(minute) { - this.minute = parseInt(minute, 10) || 0; - this.rd._setDateComponents(this); - }, - setSeconds: function(second) { - this.second = parseInt(second, 10) || 0; - this.rd._setDateComponents(this); - }, - setMilliseconds: function(milli) { - this.millisecond = parseInt(milli, 10) || 0; - this.rd._setDateComponents(this); - }, - - /** - * Return a new date instance in the current calendar 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 - */ - before: function (dow) { - return new this.constructor({ - rd: this.rd.before(dow, this.offset), - timezone: this.timezone - }); - }, - - /** - * Return a new date instance in the current calendar 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 - */ - after: function (dow) { - return new this.constructor({ - rd: this.rd.after(dow, this.offset), - timezone: this.timezone - }); - }, + return Math.floor((rd-yearStart)/7) + 1; + }, - /** - * 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 - */ - onOrBefore: function (dow) { - return new this.constructor({ - rd: this.rd.onOrBefore(dow, this.offset), - timezone: this.timezone - }); - }, + /** + * Return the ordinal number of the week within the month. The first week of a month is + * the first one that contains 4 or more days in that month. If any days precede this + * first week, they are marked as being in week 0. This function returns values from 0 + * through 6. + * + * The locale is a required parameter because different locales that use the same + * Gregorian calendar consider different days of the week to be the beginning of + * the week. This can affect the week of the month in which some days are located. + * + * @param {Locale|string} locale the locale or locale spec to use when figuring out + * the first day of the week + * @return {number} the ordinal number of the week within the current month + */ + getWeekOfMonth: function(locale) { + var li = new LocaleInfo(locale); - /** - * 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 - */ - onOrAfter: function (dow) { - return new this.constructor({ - rd: this.rd.onOrAfter(dow, this.offset), - timezone: this.timezone - }); - }, - - /** - * Return a Javascript Date object that is equivalent to this date - * object. - * - * @return {Date|undefined} a javascript Date object - */ - getJSDate: function() { - var unix = this.rd.getTimeExtended(); - return isNaN(unix) ? undefined : new Date(unix); - }, - - /** - * Return the Rata Die (fixed day) number of this date. - * - * @protected - * @return {number} the rd date as a number - */ - getRataDie: function() { - return this.rd.getRataDie(); - }, - - /** - * Set the date components of this instance based on the given rd. - * @protected - * @param {number} rd the rata die date to set - */ - setRd: function (rd) { - this.rd = this.newRd({ - rd: rd, - cal: this.cal - }); - this._calcDateComponents(); - }, - - /** - * Return the Julian Day equivalent to this calendar date as a number. - * - * @return {number} the julian date equivalent of this date - */ - getJulianDay: function() { - return this.rd.getJulianDay(); - }, - - /** - * Set the date of this instance using a Julian Day. - * @param {number|JulianDay} date the Julian Day to use to set this date - */ - setJulianDay: function (date) { - this.rd = this.newRd({ - julianday: (typeof(date) === 'object') ? date.getDate() : date, - cal: this.cal - }); - this._calcDateComponents(); - }, + var first = this.newRd({ + year: this._calcYear(this.rd.getRataDie()+this.offset), + month: this.getMonths(), + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + cal: this.cal + }); + var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); - /** - * Return the time zone associated with this date, or - * undefined if none was specified in the constructor. - * - * @return {string|undefined} the name of the time zone for this date instance - */ - getTimeZone: function() { - return this.timezone || "local"; - }, - - /** - * Set the time zone associated with this date. - * @param {string=} tzName the name of the time zone to set into this date instance, - * or "undefined" to unset the time zone - */ - setTimeZone: function (tzName) { - if (!tzName || tzName === "") { - // same as undefining it - this.timezone = undefined; - this.tz = undefined; - } else if (typeof(tzName) === 'string') { - this.timezone = tzName; - this.tz = undefined; - // assuming the same UTC time, but a new time zone, now we have to - // recalculate what the date components are - this._calcDateComponents(); - } - }, - - /** - * Return the rd number of the first Sunday of the given ISO year. - * @protected - * @param {number} year the year for which the first Sunday is being sought - * @return {number} the rd of the first Sunday of the ISO year - */ - firstSunday: function (year) { - var firstDay = this.newRd({ - year: year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - cal: this.cal - }); - var firstThu = this.newRd({ - rd: firstDay.onOrAfter(4), - cal: this.cal - }); - return firstThu.before(0); - }, - - /** - * Return the ISO 8601 week number in the current year for the current date. The week - * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some - * calendars. - * - * @return {number} the week number for the current date - */ - getWeekOfYear: function() { - var rd = Math.floor(this.rd.getRataDie()); - var year = this._calcYear(rd + this.offset); - var yearStart = this.firstSunday(year); - var nextYear; - - // if we have a January date, it may be in this ISO year or the previous year - if (rd < yearStart) { - yearStart = this.firstSunday(year-1); - } else { - // if we have a late December date, it may be in this ISO year, or the next year - nextYear = this.firstSunday(year+1); - if (rd >= nextYear) { - yearStart = nextYear; - } - } - - return Math.floor((rd-yearStart)/7) + 1; - }, - - /** - * Return the ordinal number of the week within the month. The first week of a month is - * the first one that contains 4 or more days in that month. If any days precede this - * first week, they are marked as being in week 0. This function returns values from 0 - * through 6.
- * - * The locale is a required parameter because different locales that use the same - * Gregorian calendar consider different days of the week to be the beginning of - * the week. This can affect the week of the month in which some days are located. - * - * @param {Locale|string} locale the locale or locale spec to use when figuring out - * the first day of the week - * @return {number} the ordinal number of the week within the current month - */ - getWeekOfMonth: function(locale) { - var li = new LocaleInfo(locale); - - var first = this.newRd({ - year: this._calcYear(this.rd.getRataDie()+this.offset), - month: this.getMonths(), - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - cal: this.cal - }); - var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); - - if (weekStart - first.getRataDie() > 3) { - // if the first week has 4 or more days in it of the current month, then consider - // that week 1. Otherwise, it is week 0. To make it week 1, move the week start - // one week earlier. - weekStart -= 7; - } - return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; - } + if (weekStart - first.getRataDie() > 3) { + // if the first week has 4 or more days in it of the current month, then consider + // that week 1. Otherwise, it is week 0. To make it week 1, move the week start + // one week earlier. + weekStart -= 7; + } + return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; + } }; module.exports = IDate; \ No newline at end of file diff --git a/js/lib/INumber.js b/js/lib/INumber.js index 836038b676..fa9b9d3b24 100644 --- a/js/lib/INumber.js +++ b/js/lib/INumber.js @@ -1,6 +1,6 @@ /* * INumber.js - Parse a number in any locale - * + * * Copyright © 2012-2015, 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 LocaleInfo = require("./LocaleInfo.js"); @@ -31,19 +31,19 @@ var Currency = require("./Currency.js"); /** * @class * Parse a string as a number, ignoring all locale-specific formatting.
- * - * This class is different from the standard Javascript parseInt() and parseFloat() - * functions in that the number to be parsed can have formatting characters in it + * + * This class is different from the standard Javascript parseInt() and parseFloat() + * functions in that the number to be parsed can have formatting characters in it * that are not supported by those two - * functions, and it handles numbers written in other locales properly. For example, - * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it - * will return you the number 203. The INumber class will parse it correctly and - * the value() function will return the number 203231.23. If you pass parseFloat() the + * functions, and it handles numbers written in other locales properly. For example, + * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it + * will return you the number 203. The INumber class will parse it correctly and + * the value() function will return the number 203231.23. If you pass parseFloat() the * string "203.231,23" with the locale set to de-DE, it will return you 203 again. This * class will return the correct number 203231.23 again.
- * + * * The options object may contain any of the following properties: - * + * *
*
*- locale - specify the locale of the string to parse. This is used to * figure out what the decimal point character is. If not specified, the default locale @@ -54,176 +54,176 @@ var Currency = require("./Currency.js"); * return undefined. If * the number is to be interpretted as percentage amount and there is a percentage sign * in the string, then the number will be returned - * as a fraction from the valueOf() method. If there is no percentage sign, then the - * number will be returned as a regular number. That is "58.3%" will be returned as the - * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property + * as a fraction from the valueOf() method. If there is no percentage sign, then the + * number will be returned as a regular number. That is "58.3%" will be returned as the + * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property * are "number", "currency", and "percentage". Default if this is not specified is * "number". - *
- onLoad - a callback function to call when the locale data is fully + *
- onLoad - a callback function to call when the locale 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. *
- * - * This class is named INumber ("ilib number") so as not to conflict with the + * + * This class is named INumber ("ilib number") so as not to conflict with the * built-in Javascript Number class. - * + * * @constructor * @param {string|number|INumber|Number|undefined} str a string to parse as a number, or a number value - * @param {Object=} options Options controlling how the instance should be created + * @param {Object=} options Options controlling how the instance should be created */ var INumber = function (str, options) { - var i, stripped = "", - sync = true; - - this.locale = new Locale(); - this.type = "number"; - - if (options) { - if (options.locale) { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - if (options.type) { - switch (options.type) { - case "number": - case "currency": - case "percentage": - this.type = options.type; - break; - default: - break; - } - } - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - } else { - options = {sync: true}; - } - - isDigit._init(sync, options.loadParams, ilib.bind(this, function() { - isSpace._init(sync, options.loadParams, ilib.bind(this, function() { - new LocaleInfo(this.locale, { - sync: sync, - loadParams: options.loadParams, - onLoad: ilib.bind(this, function (li) { - this.li = li; - this.decimal = li.getDecimalSeparator(); - var nativeDecimal = this.li.getNativeDecimalSeparator() || ""; - - switch (typeof(str)) { - case 'string': - // stripping should work for all locales, because you just ignore all the - // formatting except the decimal char - var unary = true; // looking for the unary minus still? - var lastNumericChar = 0; - this.str = str || "0"; - i = 0; - for (i = 0; i < this.str.length; i++) { - if (unary && this.str.charAt(i) === '-') { - unary = false; - stripped += this.str.charAt(i); - lastNumericChar = i; - } else if (isDigit(this.str.charAt(i))) { - stripped += this.str.charAt(i); - unary = false; - lastNumericChar = i; - } else if (this.str.charAt(i) === this.decimal || this.str.charAt(i) === nativeDecimal) { - stripped += "."; // always convert to period - unary = false; - lastNumericChar = i; - } // else ignore - } - // record what we actually parsed - this.parsed = this.str.substring(0, lastNumericChar+1); - - /** @type {number} */ - this.value = parseFloat(this._mapToLatinDigits(stripped)); - break; - case 'number': - this.str = "" + str; - this.value = str; - break; - - case 'object': - // call parseFloat to coerse the type to number - this.value = parseFloat(str.valueOf()); - this.str = "" + this.value; - break; - - case 'undefined': - this.value = 0; - this.str = "0"; - break; - } - - switch (this.type) { - default: - // don't need to do anything special for other types - break; - case "percentage": - if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { - this.value /= 100; - } - break; - case "currency": - stripped = ""; - i = 0; - while (i < this.str.length && - !isDigit(this.str.charAt(i)) && - !isSpace(this.str.charAt(i))) { - stripped += this.str.charAt(i++); - } - if (stripped.length === 0) { - while (i < this.str.length && - isDigit(this.str.charAt(i)) || - isSpace(this.str.charAt(i)) || - this.str.charAt(i) === '.' || - this.str.charAt(i) === ',' ) { - i++; - } - while (i < this.str.length && - !isDigit(this.str.charAt(i)) && - !isSpace(this.str.charAt(i))) { - stripped += this.str.charAt(i++); - } - } - new Currency({ - locale: this.locale, - sign: stripped, - sync: sync, - loadParams: options.loadParams, - onLoad: ilib.bind(this, function (cur) { - this.currency = cur; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - return; - } - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - })); - })); + var i, stripped = "", + sync = true; + + this.locale = new Locale(); + this.type = "number"; + + if (options) { + if (options.locale) { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + if (options.type) { + switch (options.type) { + case "number": + case "currency": + case "percentage": + this.type = options.type; + break; + default: + break; + } + } + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + } else { + options = {sync: true}; + } + + isDigit._init(sync, options.loadParams, ilib.bind(this, function() { + isSpace._init(sync, options.loadParams, ilib.bind(this, function() { + new LocaleInfo(this.locale, { + sync: sync, + loadParams: options.loadParams, + onLoad: ilib.bind(this, function (li) { + this.li = li; + this.decimal = li.getDecimalSeparator(); + var nativeDecimal = this.li.getNativeDecimalSeparator() || ""; + + switch (typeof(str)) { + case 'string': + // stripping should work for all locales, because you just ignore all the + // formatting except the decimal char + var unary = true; // looking for the unary minus still? + var lastNumericChar = 0; + this.str = str || "0"; + i = 0; + for (i = 0; i < this.str.length; i++) { + if (unary && this.str.charAt(i) === '-') { + unary = false; + stripped += this.str.charAt(i); + lastNumericChar = i; + } else if (isDigit(this.str.charAt(i))) { + stripped += this.str.charAt(i); + unary = false; + lastNumericChar = i; + } else if (this.str.charAt(i) === this.decimal || this.str.charAt(i) === nativeDecimal) { + stripped += "."; // always convert to period + unary = false; + lastNumericChar = i; + } // else ignore + } + // record what we actually parsed + this.parsed = this.str.substring(0, lastNumericChar+1); + + /** @type {number} */ + this.value = parseFloat(this._mapToLatinDigits(stripped)); + break; + case 'number': + this.str = "" + str; + this.value = str; + break; + + case 'object': + // call parseFloat to coerse the type to number + this.value = parseFloat(str.valueOf()); + this.str = "" + this.value; + break; + + case 'undefined': + this.value = 0; + this.str = "0"; + break; + } + + switch (this.type) { + default: + // don't need to do anything special for other types + break; + case "percentage": + if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { + this.value /= 100; + } + break; + case "currency": + stripped = ""; + i = 0; + while (i < this.str.length && + !isDigit(this.str.charAt(i)) && + !isSpace(this.str.charAt(i))) { + stripped += this.str.charAt(i++); + } + if (stripped.length === 0) { + while (i < this.str.length && + isDigit(this.str.charAt(i)) || + isSpace(this.str.charAt(i)) || + this.str.charAt(i) === '.' || + this.str.charAt(i) === ',' ) { + i++; + } + while (i < this.str.length && + !isDigit(this.str.charAt(i)) && + !isSpace(this.str.charAt(i))) { + stripped += this.str.charAt(i++); + } + } + new Currency({ + locale: this.locale, + sign: stripped, + sync: sync, + loadParams: options.loadParams, + onLoad: ilib.bind(this, function (cur) { + this.currency = cur; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + return; + } + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + })); + })); }; INumber.prototype = { @@ -234,65 +234,65 @@ INumber.prototype = { // only map if there are actual native digits var digits = this.li.getNativeDigits(); if (!digits) return str; - + var digitMap = {}; for (var i = 0; i < digits.length; i++) { digitMap[digits[i]] = String(i); } var decimal = this.li.getNativeDecimalSeparator(); - + return str.split("").map(function(ch) { if (ch == decimal) return "."; return digitMap[ch] || ch; }).join(""); }, - - /** - * Return the locale for this formatter instance. - * @return {Locale} the locale instance for this formatter - */ - getLocale: function () { - return this.locale; - }, - - /** - * Return the original string that this number instance was created with. - * @return {string} the original string - */ - toString: function () { - return this.str; - }, - - /** - * If the type of this INumber instance is "currency", then the parser will attempt - * to figure out which currency this amount represents. The amount can be written - * with any of the currency signs or ISO 4217 codes that are currently - * recognized by ilib, and the currency signs may occur before or after the - * numeric portion of the string. If no currency can be recognized, then the - * default currency for the locale is returned. If multiple currencies can be - * recognized (for example if the currency sign is "$"), then this method - * will prefer the one for the current locale. If multiple currencies can be - * recognized, but none are used in the current locale, then the first currency - * encountered will be used. This may produce random results, though the larger - * currencies occur earlier in the list. For example, if the sign found in the - * string is "$" and that is not the sign of the currency of the current locale - * then the US dollar will be recognized, as it is the largest currency that uses - * the "$" as its sign. - * - * @return {Currency|undefined} the currency instance for this amount, or - * undefined if this INumber object is not of type currency - */ - getCurrency: function () { - return this.currency; - }, - - /** - * Return the value of this INumber object as a primitive number instance. - * @return {number} the value of this number instance - */ - valueOf: function () { - return this.value; - } + + /** + * Return the locale for this formatter instance. + * @return {Locale} the locale instance for this formatter + */ + getLocale: function () { + return this.locale; + }, + + /** + * Return the original string that this number instance was created with. + * @return {string} the original string + */ + toString: function () { + return this.str; + }, + + /** + * If the type of this INumber instance is "currency", then the parser will attempt + * to figure out which currency this amount represents. The amount can be written + * with any of the currency signs or ISO 4217 codes that are currently + * recognized by ilib, and the currency signs may occur before or after the + * numeric portion of the string. If no currency can be recognized, then the + * default currency for the locale is returned. If multiple currencies can be + * recognized (for example if the currency sign is "$"), then this method + * will prefer the one for the current locale. If multiple currencies can be + * recognized, but none are used in the current locale, then the first currency + * encountered will be used. This may produce random results, though the larger + * currencies occur earlier in the list. For example, if the sign found in the + * string is "$" and that is not the sign of the currency of the current locale + * then the US dollar will be recognized, as it is the largest currency that uses + * the "$" as its sign. + * + * @return {Currency|undefined} the currency instance for this amount, or + * undefined if this INumber object is not of type currency + */ + getCurrency: function () { + return this.currency; + }, + + /** + * Return the value of this INumber object as a primitive number instance. + * @return {number} the value of this number instance + */ + valueOf: function () { + return this.value; + } }; module.exports = INumber; diff --git a/js/lib/ISO2022.js b/js/lib/ISO2022.js index cb6f892110..81a3416b64 100644 --- a/js/lib/ISO2022.js +++ b/js/lib/ISO2022.js @@ -1,6 +1,6 @@ /* * ISO2022.js - Implement the various ISO-2022 style mappings - * + * * Copyright © 2014-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ var Charmap = require("./Charmap.js"); /** * @class * Create a new ISO-2022 mapping instance - * + * * @constructor */ var ISO2022 = function (options) { @@ -31,7 +31,7 @@ var ISO2022 = function (options) { options = options || {sync: true}; var name = options.name || "ISO-2022-JP"; var sync = typeof(options.sync) === "boolean" ? options.sync : true; - + if (typeof(options.charset) === "object" && options.charset instanceof Charset) { this.charset = options.charset; if (typeof(options.onLoad) === "function") { @@ -57,11 +57,11 @@ ISO2022.prototype.parent = Charmap; ISO2022.prototype.constructor = ISO2022; ISO2022.prototype.mapToUnicode = function (bytes) { - // TODO: implement ISO 2022 mappings + // TODO: implement ISO 2022 mappings }; - + ISO2022.prototype.mapToNative = function(str) { - // TODO: implement ISO 2022 mappings + // TODO: implement ISO 2022 mappings }; /* diff --git a/js/lib/ISet.js b/js/lib/ISet.js index 0feb9709fa..cc2755e603 100644 --- a/js/lib/ISet.js +++ b/js/lib/ISet.js @@ -1,6 +1,6 @@ /* * ISet.js - ilib Set class definition for platforms older than ES6 - * + * * Copyright © 2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,65 +23,65 @@ * elements array, or the first element added to the set. The type * may be "string" or "number", and all elements will be returned * as elements of that type. - * + * * @class * @param {Array.
=} elements initial elements to add to the set * @constructor */ var ISet = function(elements) { - this.elements = {}; - - if (elements && elements.length) { - for (var i = 0; i < elements.length; i++) { - this.elements[elements[i]] = true; - } - - this.type = typeof(elements[0]); - } + this.elements = {}; + + if (elements && elements.length) { + for (var i = 0; i < elements.length; i++) { + this.elements[elements[i]] = true; + } + + this.type = typeof(elements[0]); + } }; /** * @private */ ISet.prototype._addOne = function(element) { - if (this.isEmpty()) { - this.type = typeof(element); - } - - if (!this.elements[element]) { - this.elements[element] = true; - return true; - } - - return false; + if (this.isEmpty()) { + this.type = typeof(element); + } + + if (!this.elements[element]) { + this.elements[element] = true; + return true; + } + + return false; }; /** - * Adds the specified element or array of elements to this set if it is or they are not + * Adds the specified element or array of elements to this set if it is or they are not * already present. - * + * * @param {*|Array.<*>} element element or array of elements to add * @return {boolean} true if this set did not already contain the specified element[s] */ ISet.prototype.add = function(element) { - var ret = false; - - if (typeof(element) === "object") { - for (var i = 0; i < element.length; i++) { - ret = this._addOne(element[i]) || ret; - } - } else { - ret = this._addOne(element); - } - - return ret; + var ret = false; + + if (typeof(element) === "object") { + for (var i = 0; i < element.length; i++) { + ret = this._addOne(element[i]) || ret; + } + } else { + ret = this._addOne(element); + } + + return ret; }; /** * Removes all of the elements from this set. */ ISet.prototype.clear = function() { - this.elements = {}; + this.elements = {}; }; /** @@ -90,7 +90,7 @@ ISet.prototype.clear = function() { * @return {boolean} */ ISet.prototype.contains = function(element) { - return this.elements[element] || false; + return this.elements[element] || false; }; ISet.prototype.has = ISet.prototype.contains; // for compatibility with ES6 @@ -100,7 +100,7 @@ ISet.prototype.has = ISet.prototype.contains; // for compatibility with ES6 * @return {boolean} */ ISet.prototype.isEmpty = function() { - return (Object.keys(this.elements).length === 0); + return (Object.keys(this.elements).length === 0); }; /** @@ -109,12 +109,12 @@ ISet.prototype.isEmpty = function() { * @return {boolean} true if the set contained the specified element */ ISet.prototype.remove = function(element) { - if (this.elements[element]) { - delete this.elements[element]; - return true; - } - - return false; + if (this.elements[element]) { + delete this.elements[element]; + return true; + } + + return false; }; /** @@ -122,18 +122,18 @@ ISet.prototype.remove = function(element) { * @return {Array.<*>} the set represented as a javascript array */ ISet.prototype.asArray = function() { - var keys = Object.keys(this.elements); - - // keys is an array of strings. Convert to numbers if necessary - if (this.type === "number") { - var tmp = []; - for (var i = 0; i < keys.length; i++) { - tmp.push(Number(keys[i]).valueOf()); - } - keys = tmp; - } - - return keys; + var keys = Object.keys(this.elements); + + // keys is an array of strings. Convert to numbers if necessary + if (this.type === "number") { + var tmp = []; + for (var i = 0; i < keys.length; i++) { + tmp.push(Number(keys[i]).valueOf()); + } + keys = tmp; + } + + return keys; }; /** @@ -141,7 +141,7 @@ ISet.prototype.asArray = function() { * @return {string} the current set represented as json */ ISet.prototype.toJson = function() { - return JSON.stringify(this.asArray()); + return JSON.stringify(this.asArray()); }; /** @@ -150,7 +150,7 @@ ISet.prototype.toJson = function() { * @return {*} the JS representation of this object */ ISet.prototype.toJS = function() { - return this.asArray(); + return this.asArray(); }; /** @@ -158,7 +158,7 @@ ISet.prototype.toJS = function() { * @return {ISet|undefined} the current object, or undefined if the conversion did not work */ ISet.prototype.fromJS = function(obj) { - return this.add(obj) ? this : undefined; + return this.add(obj) ? this : undefined; }; module.exports = ISet; diff --git a/js/lib/IString.js b/js/lib/IString.js index 95b49d3e61..46cf7de100 100644 --- a/js/lib/IString.js +++ b/js/lib/IString.js @@ -19,7 +19,7 @@ // !data plurals -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var MathUtils = require("./MathUtils.js"); var Locale = require("./Locale.js"); @@ -42,20 +42,20 @@ var Locale = require("./Locale.js"); * @param {string|IString=} string initialize this instance with this string */ var IString = function (string) { - if (typeof(string) === 'object') { - if (string instanceof IString) { - this.str = string.str; - } else { - this.str = string.toString(); - } - } else if (typeof(string) === 'string') { - this.str = new String(string); - } else { - this.str = ""; - } - this.length = this.str.length; - this.cpLength = -1; - this.localeSpec = ilib.getLocale(); + if (typeof(string) === 'object') { + if (string instanceof IString) { + this.str = string.str; + } else { + this.str = string.toString(); + } + } else if (typeof(string) === 'string') { + this.str = new String(string); + } else { + this.str = ""; + } + this.length = this.str.length; + this.cpLength = -1; + this.localeSpec = ilib.getLocale(); }; /** @@ -68,8 +68,8 @@ var IString = function (string) { * @return {boolean} true if the character is a surrogate */ IString._isSurrogate = function (ch) { - var n = ch.charCodeAt(0); - return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); + var n = ch.charCodeAt(0); + return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); }; // build in the English rule @@ -115,15 +115,15 @@ IString.plurals_default = { * @return {string} a string containing the character represented by the codepoint */ IString.fromCodePoint = function (codepoint) { - if (codepoint < 0x10000) { - return String.fromCharCode(codepoint); - } else { - var high = Math.floor(codepoint / 0x10000) - 1; - var low = codepoint & 0xFFFF; - - return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + - String.fromCharCode(0xDC00 | (low & 0x3FF)); - } + if (codepoint < 0x10000) { + return String.fromCharCode(codepoint); + } else { + var high = Math.floor(codepoint / 0x10000) - 1; + var low = codepoint & 0xFFFF; + + return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + + String.fromCharCode(0xDC00 | (low & 0x3FF)); + } }; /** @@ -138,23 +138,23 @@ IString.fromCodePoint = function (codepoint) { * given index into the string */ IString.toCodePoint = function(str, index) { - if (!str || str.length === 0) { - return -1; - } - var code = -1, high = str.charCodeAt(index); - if (high >= 0xD800 && high <= 0xDBFF) { - if (str.length > index+1) { - var low = str.charCodeAt(index+1); - if (low >= 0xDC00 && low <= 0xDFFF) { - code = (((high & 0x3C0) >> 6) + 1) << 16 | - (((high & 0x3F) << 10) | (low & 0x3FF)); - } - } - } else { - code = high; - } - - return code; + if (!str || str.length === 0) { + return -1; + } + var code = -1, high = str.charCodeAt(index); + if (high >= 0xD800 && high <= 0xDBFF) { + if (str.length > index+1) { + var low = str.charCodeAt(index+1); + if (low >= 0xDC00 && low <= 0xDFFF) { + code = (((high & 0x3C0) >> 6) + 1) << 16 | + (((high & 0x3F) << 10) | (low & 0x3FF)); + } + } + } else { + code = high; + } + + return code; }; /** @@ -165,26 +165,26 @@ IString.toCodePoint = function(str, index) { * @param {function(*)=} onLoad */ IString.loadPlurals = function (sync, locale, loadParams, onLoad) { - var loc; - if (locale) { - loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; - } else { - loc = new Locale(ilib.getLocale()); - } - var spec = loc.getLanguage(); - Utils.loadData({ - name: "plurals.json", - object: "IString", - locale: loc, - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function(plurals) { - plurals = plurals || IString.plurals_default; - if (onLoad && typeof(onLoad) === 'function') { - onLoad(plurals); - } - }) - }); + var loc; + if (locale) { + loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; + } else { + loc = new Locale(ilib.getLocale()); + } + var spec = loc.getLanguage(); + Utils.loadData({ + name: "plurals.json", + object: "IString", + locale: loc, + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function(plurals) { + plurals = plurals || IString.plurals_default; + if (onLoad && typeof(onLoad) === 'function') { + onLoad(plurals); + } + }) + }); }; /** @@ -192,428 +192,428 @@ IString.loadPlurals = function (sync, locale, loadParams, onLoad) { * @static */ IString._fncs = { - /** - * @private - * @param {Object} obj - * @return {string|undefined} - */ - firstProp: function (obj) { - for (var p in obj) { - if (p && obj[p]) { - return p; - } - } - return undefined; // should never get here - }, - - /** - * @private - * @param {Object} obj - * @return {string|undefined} - */ - firstPropRule: function (obj) { - if (Object.prototype.toString.call(obj) === '[object Array]') { - return "inrange"; - } else if (Object.prototype.toString.call(obj) === '[object Object]') { - for (var p in obj) { - if (p && obj[p]) { - return p; - } - } - - } - return undefined; // should never get here - }, - - /** - * @private - * @param {Object} obj - * @param {number|Object} n - * @return {?} - */ - getValue: function (obj, n) { - if (typeof(obj) === 'object') { - var subrule = IString._fncs.firstPropRule(obj); - if (subrule === "inrange") { - return IString._fncs[subrule](obj, n); - } - return IString._fncs[subrule](obj[subrule], n); - } else if (typeof(obj) === 'string') { - if (typeof(n) === 'object'){ - return n[obj]; - } - return n; - } else { - return obj; - } - }, - - /** - * @private - * @param {number|Object} n - * @param {Array. >|Object} range - * @return {boolean} - */ - matchRangeContinuous: function(n, range) { - - for (var num in range) { - if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { - var obj = range[num]; - if (typeof(obj) === 'number') { - if (n === range[num]) { - return true; - } else if (n >= range[0] && n <= range[1]) { - return true; - } - } else if (Object.prototype.toString.call(obj) === '[object Array]') { - if (n >= obj[0] && n <= obj[1]) { - return true; - } - } - } - } - return false; - }, - - /** - * @private - * @param {*} number - * @return {Object} - */ - calculateNumberDigits: function(number) { - var numberToString = number.toString(); - var parts = []; - var numberDigits = {}; - var operandSymbol = {}; - - if (numberToString.indexOf('.') !== -1) { //decimal - parts = numberToString.split('.', 2); - numberDigits.integerPart = parseInt(parts[0], 10); - numberDigits.decimalPartLength = parts[1].length; - numberDigits.decimalPart = parseInt(parts[1], 10); - - operandSymbol.n = parseFloat(number); - operandSymbol.i = numberDigits.integerPart; - operandSymbol.v = numberDigits.decimalPartLength; - operandSymbol.w = numberDigits.decimalPartLength; - operandSymbol.f = numberDigits.decimalPart; - operandSymbol.t = numberDigits.decimalPart; - - } else { - numberDigits.integerPart = number; - numberDigits.decimalPartLength = 0; - numberDigits.decimalPart = 0; - - operandSymbol.n = parseInt(number, 10); - operandSymbol.i = numberDigits.integerPart; - operandSymbol.v = 0; - operandSymbol.w = 0; - operandSymbol.f = 0; - operandSymbol.t = 0; - - } - return operandSymbol - }, - - /** - * @private - * @param {number|Object} n - * @param {Array. >|Object} range - * @return {boolean} - */ - matchRange: function(n, range) { - return IString._fncs.matchRangeContinuous(n, range); - }, - - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {boolean} - */ - is: function(rule, n) { - var left = IString._fncs.getValue(rule[0], n); - var right = IString._fncs.getValue(rule[1], n); - return left == right; - }, - - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {boolean} - */ - isnot: function(rule, n) { - return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); - }, - - /** - * @private - * @param {Object} rule - * @param {number|Object} n - * @return {boolean} - */ - inrange: function(rule, n) { - if (typeof(rule[0]) === 'number') { - if(typeof(n) === 'object') { - return IString._fncs.matchRange(n.n,rule); - } - return IString._fncs.matchRange(n,rule); - } else if (typeof(rule[0]) === 'undefined') { - var subrule = IString._fncs.firstPropRule(rule); - return IString._fncs[subrule](rule[subrule], n); - } else { - return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); - } - }, - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {boolean} - */ - notin: function(rule, n) { - return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); - }, - - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {boolean} - */ - within: function(rule, n) { - return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); - }, - - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {number} - */ - mod: function(rule, n) { - return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); - }, - - /** - * @private - * @param {Object} rule - * @param {number} n - * @return {number} - */ - n: function(rule, n) { - return n; - }, - - /** - * @private - * @param {Object} rule - * @param {number|Object} n - * @return {boolean} - */ - or: function(rule, n) { - var ruleLength = rule.length; - var result, i; - for (i=0; i < ruleLength; i++) { - result = IString._fncs.getValue(rule[i], n); - if (result) { - return true; - } - } - return false; - }, - /** - * @private - * @param {Object} rule - * @param {number|Object} n - * @return {boolean} - */ - and: function(rule, n) { - var ruleLength = rule.length; - var result, i; - for (i=0; i < ruleLength; i++) { - result= IString._fncs.getValue(rule[i], n); - if (!result) { - return false; - } - } - return true; - }, - /** - * @private - * @param {Object} rule - * @param {number|Object} n - * @return {boolean} - */ - eq: function(rule, n) { - var valueLeft = IString._fncs.getValue(rule[0], n); - var valueRight; - - if (typeof(rule[0]) === 'string') { - if (typeof(n) === 'object'){ - valueRight = n[rule[0]]; - if (typeof(rule[1])=== 'number'){ - valueRight = IString._fncs.getValue(rule[1], n); - } else if (typeof(rule[1])=== 'object' && (IString._fncs.firstPropRule(rule[1]) === "inrange" )){ - valueRight = IString._fncs.getValue(rule[1], n); - } - } - } else { - if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod - valueRight = IString._fncs.getValue(rule[1], valueLeft); - } else { - valueRight = IString._fncs.getValue(rule[1], n); - } - } - if(typeof(valueRight) === 'boolean') { - return (valueRight ? true : false); - } else { - return (valueLeft == valueRight ? true :false); - } - }, - /** - * @private - * @param {Object} rule - * @param {number|Object} n - * @return {boolean} - */ - neq: function(rule, n) { - var valueLeft = IString._fncs.getValue(rule[0], n); - var valueRight; - var leftRange; - var rightRange; - - if (typeof(rule[0]) === 'string') { - valueRight = n[rule[0]]; - if (typeof(rule[1])=== 'number'){ - valueRight = IString._fncs.getValue(rule[1], n); - } else if (typeof(rule[1]) === 'object') { - leftRange = rule[1][0]; - rightRange = rule[1][1]; - if (typeof(leftRange) === 'number' && - typeof(rightRange) === 'number'){ - - if (valueLeft >= leftRange && valueRight <= rightRange) { - return false - } else { - return true; - } - } - } - } else { - if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod - valueRight = IString._fncs.getValue(rule[1], valueLeft); - } else { - valueRight = IString._fncs.getValue(rule[1], n); - } - } - - if(typeof(valueRight) === 'boolean') {//mod - return (valueRight? false : true); - } else { - return (valueLeft !== valueRight ? true :false); - } - - } + /** + * @private + * @param {Object} obj + * @return {string|undefined} + */ + firstProp: function (obj) { + for (var p in obj) { + if (p && obj[p]) { + return p; + } + } + return undefined; // should never get here + }, + + /** + * @private + * @param {Object} obj + * @return {string|undefined} + */ + firstPropRule: function (obj) { + if (Object.prototype.toString.call(obj) === '[object Array]') { + return "inrange"; + } else if (Object.prototype.toString.call(obj) === '[object Object]') { + for (var p in obj) { + if (p && obj[p]) { + return p; + } + } + + } + return undefined; // should never get here + }, + + /** + * @private + * @param {Object} obj + * @param {number|Object} n + * @return {?} + */ + getValue: function (obj, n) { + if (typeof(obj) === 'object') { + var subrule = IString._fncs.firstPropRule(obj); + if (subrule === "inrange") { + return IString._fncs[subrule](obj, n); + } + return IString._fncs[subrule](obj[subrule], n); + } else if (typeof(obj) === 'string') { + if (typeof(n) === 'object'){ + return n[obj]; + } + return n; + } else { + return obj; + } + }, + + /** + * @private + * @param {number|Object} n + * @param {Array. >|Object} range + * @return {boolean} + */ + matchRangeContinuous: function(n, range) { + + for (var num in range) { + if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { + var obj = range[num]; + if (typeof(obj) === 'number') { + if (n === range[num]) { + return true; + } else if (n >= range[0] && n <= range[1]) { + return true; + } + } else if (Object.prototype.toString.call(obj) === '[object Array]') { + if (n >= obj[0] && n <= obj[1]) { + return true; + } + } + } + } + return false; + }, + + /** + * @private + * @param {*} number + * @return {Object} + */ + calculateNumberDigits: function(number) { + var numberToString = number.toString(); + var parts = []; + var numberDigits = {}; + var operandSymbol = {}; + + if (numberToString.indexOf('.') !== -1) { //decimal + parts = numberToString.split('.', 2); + numberDigits.integerPart = parseInt(parts[0], 10); + numberDigits.decimalPartLength = parts[1].length; + numberDigits.decimalPart = parseInt(parts[1], 10); + + operandSymbol.n = parseFloat(number); + operandSymbol.i = numberDigits.integerPart; + operandSymbol.v = numberDigits.decimalPartLength; + operandSymbol.w = numberDigits.decimalPartLength; + operandSymbol.f = numberDigits.decimalPart; + operandSymbol.t = numberDigits.decimalPart; + + } else { + numberDigits.integerPart = number; + numberDigits.decimalPartLength = 0; + numberDigits.decimalPart = 0; + + operandSymbol.n = parseInt(number, 10); + operandSymbol.i = numberDigits.integerPart; + operandSymbol.v = 0; + operandSymbol.w = 0; + operandSymbol.f = 0; + operandSymbol.t = 0; + + } + return operandSymbol + }, + + /** + * @private + * @param {number|Object} n + * @param {Array. >|Object} range + * @return {boolean} + */ + matchRange: function(n, range) { + return IString._fncs.matchRangeContinuous(n, range); + }, + + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {boolean} + */ + is: function(rule, n) { + var left = IString._fncs.getValue(rule[0], n); + var right = IString._fncs.getValue(rule[1], n); + return left == right; + }, + + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {boolean} + */ + isnot: function(rule, n) { + return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); + }, + + /** + * @private + * @param {Object} rule + * @param {number|Object} n + * @return {boolean} + */ + inrange: function(rule, n) { + if (typeof(rule[0]) === 'number') { + if(typeof(n) === 'object') { + return IString._fncs.matchRange(n.n,rule); + } + return IString._fncs.matchRange(n,rule); + } else if (typeof(rule[0]) === 'undefined') { + var subrule = IString._fncs.firstPropRule(rule); + return IString._fncs[subrule](rule[subrule], n); + } else { + return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); + } + }, + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {boolean} + */ + notin: function(rule, n) { + return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); + }, + + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {boolean} + */ + within: function(rule, n) { + return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); + }, + + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {number} + */ + mod: function(rule, n) { + return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); + }, + + /** + * @private + * @param {Object} rule + * @param {number} n + * @return {number} + */ + n: function(rule, n) { + return n; + }, + + /** + * @private + * @param {Object} rule + * @param {number|Object} n + * @return {boolean} + */ + or: function(rule, n) { + var ruleLength = rule.length; + var result, i; + for (i=0; i < ruleLength; i++) { + result = IString._fncs.getValue(rule[i], n); + if (result) { + return true; + } + } + return false; + }, + /** + * @private + * @param {Object} rule + * @param {number|Object} n + * @return {boolean} + */ + and: function(rule, n) { + var ruleLength = rule.length; + var result, i; + for (i=0; i < ruleLength; i++) { + result= IString._fncs.getValue(rule[i], n); + if (!result) { + return false; + } + } + return true; + }, + /** + * @private + * @param {Object} rule + * @param {number|Object} n + * @return {boolean} + */ + eq: function(rule, n) { + var valueLeft = IString._fncs.getValue(rule[0], n); + var valueRight; + + if (typeof(rule[0]) === 'string') { + if (typeof(n) === 'object'){ + valueRight = n[rule[0]]; + if (typeof(rule[1])=== 'number'){ + valueRight = IString._fncs.getValue(rule[1], n); + } else if (typeof(rule[1])=== 'object' && (IString._fncs.firstPropRule(rule[1]) === "inrange" )){ + valueRight = IString._fncs.getValue(rule[1], n); + } + } + } else { + if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod + valueRight = IString._fncs.getValue(rule[1], valueLeft); + } else { + valueRight = IString._fncs.getValue(rule[1], n); + } + } + if(typeof(valueRight) === 'boolean') { + return (valueRight ? true : false); + } else { + return (valueLeft == valueRight ? true :false); + } + }, + /** + * @private + * @param {Object} rule + * @param {number|Object} n + * @return {boolean} + */ + neq: function(rule, n) { + var valueLeft = IString._fncs.getValue(rule[0], n); + var valueRight; + var leftRange; + var rightRange; + + if (typeof(rule[0]) === 'string') { + valueRight = n[rule[0]]; + if (typeof(rule[1])=== 'number'){ + valueRight = IString._fncs.getValue(rule[1], n); + } else if (typeof(rule[1]) === 'object') { + leftRange = rule[1][0]; + rightRange = rule[1][1]; + if (typeof(leftRange) === 'number' && + typeof(rightRange) === 'number'){ + + if (valueLeft >= leftRange && valueRight <= rightRange) { + return false + } else { + return true; + } + } + } + } else { + if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod + valueRight = IString._fncs.getValue(rule[1], valueLeft); + } else { + valueRight = IString._fncs.getValue(rule[1], n); + } + } + + if(typeof(valueRight) === 'boolean') {//mod + return (valueRight? false : true); + } else { + return (valueLeft !== valueRight ? true :false); + } + + } }; IString.prototype = { - /** - * Return the length of this string in characters. This function defers to the regular - * Javascript string class in order to perform the length function. Please note that this - * method is a real method, whereas the length property of Javascript strings is - * implemented by native code and appears as a property. - * - * Example: - * - *
- * var str = new IString("this is a string"); - * console.log("String is " + str._length() + " characters long."); - *- * @private - */ - _length: function () { - return this.str.length; - }, - - /** - * Format this string instance as a message, replacing the parameters with - * the given values.- * - * The string can contain any text that a regular Javascript string can - * contain. Replacement parameters have the syntax: - * - *
- * {name} - *- * - * Where "name" can be any string surrounded by curly brackets. The value of - * "name" is taken from the parameters argument.- * - * Example: - * - *
- * var str = new IString("There are {num} objects."); - * console.log(str.format({ - * num: 12 - * }); - *- * - * Would give the output: - * - *- * There are 12 objects. - *- * - * If a property is missing from the parameter block, the replacement - * parameter substring is left untouched in the string, and a different - * set of parameters may be applied a second time. This way, different - * parts of the code may format different parts of the message that they - * happen to know about.- * - * Example: - * - *
- * var str = new IString("There are {num} objects in the {container}."); - * console.log(str.format({ - * num: 12 - * }); - *- * - * Would give the output:- * - *
- * There are 12 objects in the {container}. - *- * - * The result can then be formatted again with a different parameter block that - * specifies a value for the container property. - * - * @param params a Javascript object containing values for the replacement - * parameters in the current string - * @return a new IString instance with as many replacement parameters filled - * out as possible with real values. - */ - format: function (params) { - var formatted = this.str; - if (params) { - var regex; - for (var p in params) { - if (typeof(params[p]) !== 'undefined') { - regex = new RegExp("\{"+p+"\}", "g"); - formatted = formatted.replace(regex, params[p]); - } - } - } - return formatted.toString(); - }, - - /** @private */ + /** + * Return the length of this string in characters. This function defers to the regular + * Javascript string class in order to perform the length function. Please note that this + * method is a real method, whereas the length property of Javascript strings is + * implemented by native code and appears as a property.+ * + * Example: + * + *
+ * var str = new IString("this is a string"); + * console.log("String is " + str._length() + " characters long."); + *+ * @private + */ + _length: function () { + return this.str.length; + }, + + /** + * Format this string instance as a message, replacing the parameters with + * the given values.+ * + * The string can contain any text that a regular Javascript string can + * contain. Replacement parameters have the syntax: + * + *
+ * {name} + *+ * + * Where "name" can be any string surrounded by curly brackets. The value of + * "name" is taken from the parameters argument.+ * + * Example: + * + *
+ * var str = new IString("There are {num} objects."); + * console.log(str.format({ + * num: 12 + * }); + *+ * + * Would give the output: + * + *+ * There are 12 objects. + *+ * + * If a property is missing from the parameter block, the replacement + * parameter substring is left untouched in the string, and a different + * set of parameters may be applied a second time. This way, different + * parts of the code may format different parts of the message that they + * happen to know about.+ * + * Example: + * + *
+ * var str = new IString("There are {num} objects in the {container}."); + * console.log(str.format({ + * num: 12 + * }); + *+ * + * Would give the output:+ * + *
+ * There are 12 objects in the {container}. + *+ * + * The result can then be formatted again with a different parameter block that + * specifies a value for the container property. + * + * @param params a Javascript object containing values for the replacement + * parameters in the current string + * @return a new IString instance with as many replacement parameters filled + * out as possible with real values. + */ + format: function (params) { + var formatted = this.str; + if (params) { + var regex; + for (var p in params) { + if (typeof(params[p]) !== 'undefined') { + regex = new RegExp("\{"+p+"\}", "g"); + formatted = formatted.replace(regex, params[p]); + } + } + } + return formatted.toString(); + }, + + /** @private */ _testChoice: function(index, limit) { var numberDigits = {}; var operandValue = {}; - + switch (typeof(index)) { case 'number': operandValue = IString._fncs.calculateNumberDigits(index); @@ -676,109 +676,109 @@ IString.prototype = { return false; }, - /** - * Format a string as one of a choice of strings dependent on the value of - * a particular argument index or array of indices.- * - * The syntax of the choice string is as follows. The string contains a - * series of choices separated by a vertical bar character "|". Each choice - * has a value or range of values to match followed by a hash character "#" - * followed by the string to use if the variable matches the criteria.
- * - * Example string: - * - *
- * var num = 2; - * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); - * console.log(str.formatChoice(num, { - * number: num - * })); - *- * - * Gives the output: - * - *- * "There are 2 objects." - *- * - * The strings to format may contain replacement variables that will be formatted - * using the format() method above and the params argument as a source of values - * to use while formatting those variables.- * - * If the criterion for a particular choice is empty, that choice will be used - * as the default one for use when none of the other choice's criteria match.
- * - * Example string: - * - *
- * var num = 22; - * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); - * console.log(str.formatChoice(num, { - * number: num - * })); - *- * - * Gives the output: - * - *- * "There are 22 objects." - *- * - * If multiple choice patterns can match a given argument index, the first one - * encountered in the string will be used. If no choice patterns match the - * argument index, then the default choice will be used. If there is no default - * choice defined, then this method will return an empty string.- * - * Special Syntax
- * - * For any choice format string, all of the patterns in the string should be - * of a single type: numeric, boolean, or string/regexp. The type of the - * patterns is determined by the type of the argument index parameter.
- * - * If the argument index is numeric, then some special syntax can be used - * in the patterns to match numeric ranges.
- * - *
- *
+ * + * A number class defines a set of numbers that receive a particular syntax + * in the strings. For example, in Slovenian, integers ending in the digit + * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. + * Similarly, integers ending in the digit "2" are in the "two" class. + * Integers ending in the digits "3" or "4" are in the "few" class, and + * every other integer is handled by the default string.- >x - match any number that is greater than x - *
- >=x - match any number that is greater than or equal to x - *
- <x - match any number that is less than x - *
- <=x - match any number that is less than or equal to x - *
- start-end - match any number in the range [start,end) - *
- zero - match any number in the class "zero". (See below for - * a description of number classes.) - *
- one - match any number in the class "one" - *
- two - match any number in the class "two" - *
- few - match any number in the class "few" - *
- many - match any number in the class "many" + /** + * Format a string as one of a choice of strings dependent on the value of + * a particular argument index or array of indices.
+ * + * The syntax of the choice string is as follows. The string contains a + * series of choices separated by a vertical bar character "|". Each choice + * has a value or range of values to match followed by a hash character "#" + * followed by the string to use if the variable matches the criteria.
+ * + * Example string: + * + *
+ * var num = 2; + * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); + * console.log(str.formatChoice(num, { + * number: num + * })); + *+ * + * Gives the output: + * + *+ * "There are 2 objects." + *+ * + * The strings to format may contain replacement variables that will be formatted + * using the format() method above and the params argument as a source of values + * to use while formatting those variables.+ * + * If the criterion for a particular choice is empty, that choice will be used + * as the default one for use when none of the other choice's criteria match.
+ * + * Example string: + * + *
+ * var num = 22; + * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); + * console.log(str.formatChoice(num, { + * number: num + * })); + *+ * + * Gives the output: + * + *+ * "There are 22 objects." + *+ * + * If multiple choice patterns can match a given argument index, the first one + * encountered in the string will be used. If no choice patterns match the + * argument index, then the default choice will be used. If there is no default + * choice defined, then this method will return an empty string.+ * + * Special Syntax
+ * + * For any choice format string, all of the patterns in the string should be + * of a single type: numeric, boolean, or string/regexp. The type of the + * patterns is determined by the type of the argument index parameter.
+ * + * If the argument index is numeric, then some special syntax can be used + * in the patterns to match numeric ranges.
+ * + *
+ *
- * - * A number class defines a set of numbers that receive a particular syntax - * in the strings. For example, in Slovenian, integers ending in the digit - * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. - * Similarly, integers ending in the digit "2" are in the "two" class. - * Integers ending in the digits "3" or "4" are in the "few" class, and - * every other integer is handled by the default string.- >x - match any number that is greater than x + *
- >=x - match any number that is greater than or equal to x + *
- <x - match any number that is less than x + *
- <=x - match any number that is less than or equal to x + *
- start-end - match any number in the range [start,end) + *
- zero - match any number in the class "zero". (See below for + * a description of number classes.) + *
- one - match any number in the class "one" + *
- two - match any number in the class "two" + *
- few - match any number in the class "few" + *
- many - match any number in the class "many" *
- other - match any number in the other or default class - *
- * - * The definition of what numbers are included in a class is locale-dependent. - * They are defined in the data file plurals.json. If your string is in a - * different locale than the default for ilib, you should call the setLocale() - * method of the string instance before calling this method.
- * - * Other Pattern Types
- * - * If the argument index is a boolean, the string values "true" and "false" - * may appear as the choice patterns.
- * - * If the argument index is of type string, then the choice patterns may contain - * regular expressions, or static strings as degenerate regexps.
- * - * Multiple Indexes
- * - * If you have 2 or more indexes to format into a string, you can pass them as - * an array. When you do that, the patterns to match should be a comma-separate - * list of patterns as per the rules above.
- * + *
+ * + * The definition of what numbers are included in a class is locale-dependent. + * They are defined in the data file plurals.json. If your string is in a + * different locale than the default for ilib, you should call the setLocale() + * method of the string instance before calling this method.
+ * + * Other Pattern Types
+ * + * If the argument index is a boolean, the string values "true" and "false" + * may appear as the choice patterns.
+ * + * If the argument index is of type string, then the choice patterns may contain + * regular expressions, or static strings as degenerate regexps.
+ * + * Multiple Indexes
+ * + * If you have 2 or more indexes to format into a string, you can pass them as + * an array. When you do that, the patterns to match should be a comma-separate + * list of patterns as per the rules above.
+ * * Example string: * *
@@ -805,47 +805,47 @@ IString.prototype = { * blank if you so choose. In the example above, the pattern for the third string could * easily have been written as ",one" instead of "other,one" and the result will be the same. * - * @param {*|Array.<*>} argIndex The index into the choice array of the current parameter, - * or an array of indices - * @param {Object} params The hash of parameter values that replace the replacement - * variables in the string - * @throws "syntax error in choice format pattern: " if there is a syntax error - * @return {string} the formatted string - */ - formatChoice: function(argIndex, params) { - var choices = this.str.split("|"); - var limits = []; - var strings = []; - var i; - var parts; - var limit; - var result = undefined; - var defaultCase = ""; - - if (this.str.length === 0) { - // nothing to do - return ""; - } - - // first parse all the choices - for (i = 0; i < choices.length; i++) { - parts = choices[i].split("#"); - if (parts.length > 2) { - limits[i] = parts[0]; - parts = parts.shift(); - strings[i] = parts.join("#"); - } else if (parts.length === 2) { - limits[i] = parts[0]; - strings[i] = parts[1]; - } else { - // syntax error - throw "syntax error in choice format pattern: " + choices[i]; - } - } - - var args = (ilib.isArray(argIndex)) ? argIndex : [argIndex]; - - // then apply the argument index (or indices) + * @param {*|Array.<*>} argIndex The index into the choice array of the current parameter, + * or an array of indices + * @param {Object} params The hash of parameter values that replace the replacement + * variables in the string + * @throws "syntax error in choice format pattern: " if there is a syntax error + * @return {string} the formatted string + */ + formatChoice: function(argIndex, params) { + var choices = this.str.split("|"); + var limits = []; + var strings = []; + var i; + var parts; + var limit; + var result = undefined; + var defaultCase = ""; + + if (this.str.length === 0) { + // nothing to do + return ""; + } + + // first parse all the choices + for (i = 0; i < choices.length; i++) { + parts = choices[i].split("#"); + if (parts.length > 2) { + limits[i] = parts[0]; + parts = parts.shift(); + strings[i] = parts.join("#"); + } else if (parts.length === 2) { + limits[i] = parts[0]; + strings[i] = parts[1]; + } else { + // syntax error + throw "syntax error in choice format pattern: " + choices[i]; + } + } + + var args = (ilib.isArray(argIndex)) ? argIndex : [argIndex]; + + // then apply the argument index (or indices) for (i = 0; i < limits.length; i++) { if (limits[i].length === 0) { // this is default case @@ -869,432 +869,432 @@ IString.prototype = { result = defaultCase || new IString(""); } - result = result.format(params); - - return result.toString(); - }, - - // delegates - /** - * Same as String.toString() - * @return {string} this instance as regular Javascript string - */ - toString: function () { - return this.str.toString(); - }, - - /** - * Same as String.valueOf() - * @return {string} this instance as a regular Javascript string - */ - valueOf: function () { - return this.str.valueOf(); - }, - - /** - * Same as String.charAt() - * @param {number} index the index of the character being sought - * @return {IString} the character at the given index - */ - charAt: function(index) { - return new IString(this.str.charAt(index)); - }, - - /** - * Same as String.charCodeAt(). This only reports on - * 2-byte UCS-2 Unicode values, and does not take into - * account supplementary characters encoded in UTF-16. - * If you would like to take account of those characters, - * use codePointAt() instead. - * @param {number} index the index of the character being sought - * @return {number} the character code of the character at the - * given index in the string - */ - charCodeAt: function(index) { - return this.str.charCodeAt(index); - }, - - /** - * Same as String.concat() - * @param {string} strings strings to concatenate to the current one - * @return {IString} a concatenation of the given strings - */ - concat: function(strings) { - return new IString(this.str.concat(strings)); - }, - - /** - * Same as String.indexOf() - * @param {string} searchValue string to search for - * @param {number} start index into the string to start searching, or - * undefined to search the entire string - * @return {number} index into the string of the string being sought, - * or -1 if the string is not found - */ - indexOf: function(searchValue, start) { - return this.str.indexOf(searchValue, start); - }, - - /** - * Same as String.lastIndexOf() - * @param {string} searchValue string to search for - * @param {number} start index into the string to start searching, or - * undefined to search the entire string - * @return {number} index into the string of the string being sought, - * or -1 if the string is not found - */ - lastIndexOf: function(searchValue, start) { - return this.str.lastIndexOf(searchValue, start); - }, - - /** - * Same as String.match() - * @param {string} regexp the regular expression to match - * @return {Array.} an array of matches - */ - match: function(regexp) { - return this.str.match(regexp); - }, - - /** - * Same as String.replace() - * @param {string} searchValue a regular expression to search for - * @param {string} newValue the string to replace the matches with - * @return {IString} a new string with all the matches replaced - * with the new value - */ - replace: function(searchValue, newValue) { - return new IString(this.str.replace(searchValue, newValue)); - }, - - /** - * Same as String.search() - * @param {string} regexp the regular expression to search for - * @return {number} position of the match, or -1 for no match - */ - search: function(regexp) { - return this.str.search(regexp); - }, - - /** - * Same as String.slice() - * @param {number} start first character to include in the string - * @param {number} end include all characters up to, but not including - * the end character - * @return {IString} a slice of the current string - */ - slice: function(start, end) { - return new IString(this.str.slice(start, end)); - }, - - /** - * Same as String.split() - * @param {string} separator regular expression to match to find - * separations between the parts of the text - * @param {number} limit maximum number of items in the final - * output array. Any items beyond that limit will be ignored. - * @return {Array. } the parts of the current string split - * by the separator - */ - split: function(separator, limit) { - return this.str.split(separator, limit); - }, - - /** - * Same as String.substr() - * @param {number} start the index of the character that should - * begin the returned substring - * @param {number} length the number of characters to return after - * the start character. - * @return {IString} the requested substring - */ - substr: function(start, length) { - var plat = ilib._getPlatform(); - if (plat === "qt" || plat === "rhino" || plat === "trireme") { - // qt and rhino have a broken implementation of substr(), so - // work around it - if (typeof(length) === "undefined") { - length = this.str.length - start; - } - } - return new IString(this.str.substr(start, length)); - }, - - /** - * Same as String.substring() - * @param {number} from the index of the character that should - * begin the returned substring - * @param {number} to the index where to stop the extraction. If - * omitted, extracts the rest of the string - * @return {IString} the requested substring - */ - substring: function(from, to) { - return this.str.substring(from, to); - }, - - /** - * Same as String.toLowerCase(). Note that this method is - * not locale-sensitive. - * @return {IString} a string with the first character - * lower-cased - */ - toLowerCase: function() { - return this.str.toLowerCase(); - }, - - /** - * Same as String.toUpperCase(). Note that this method is - * not locale-sensitive. Use toLocaleUpperCase() instead - * to get locale-sensitive behaviour. - * @return {IString} a string with the first character - * upper-cased - */ - toUpperCase: function() { - return this.str.toUpperCase(); - }, - - /** - * Convert the character or the surrogate pair at the given - * index into the string to a Unicode UCS-4 code point. - * @protected - * @param {number} index index into the string - * @return {number} code point of the character at the - * given index into the string - */ - _toCodePoint: function (index) { - return IString.toCodePoint(this.str, index); - }, - - /** - * Call the callback with each character in the string one at - * a time, taking care to step through the surrogate pairs in - * the UTF-16 encoding properly. - * - * The standard Javascript String's charAt() method only - * returns a particular 16-bit character in the - * UTF-16 encoding scheme. - * If the index to charAt() is pointing to a low- or - * high-surrogate character, - * it will return the surrogate character rather - * than the the character - * in the supplementary planes that the two surrogates together - * encode. This function will call the callback with the full - * character, making sure to join two - * surrogates into one character in the supplementary planes - * where necessary.
- * - * @param {function(string)} callback a callback function to call with each - * full character in the current string - */ - forEach: function(callback) { - if (typeof(callback) === 'function') { - var it = this.charIterator(); - while (it.hasNext()) { - callback(it.next()); - } - } - }, - - /** - * Call the callback with each numeric code point in the string one at - * a time, taking care to step through the surrogate pairs in - * the UTF-16 encoding properly.
- * - * The standard Javascript String's charCodeAt() method only - * returns information about a particular 16-bit character in the - * UTF-16 encoding scheme. - * If the index to charCodeAt() is pointing to a low- or - * high-surrogate character, - * it will return the code point of the surrogate character rather - * than the code point of the character - * in the supplementary planes that the two surrogates together - * encode. This function will call the callback with the full - * code point of each character, making sure to join two - * surrogates into one code point in the supplementary planes.
- * - * @param {function(string)} callback a callback function to call with each - * code point in the current string - */ - forEachCodePoint: function(callback) { - if (typeof(callback) === 'function') { - var it = this.iterator(); - while (it.hasNext()) { - callback(it.next()); - } - } - }, - - /** - * Return an iterator that will step through all of the characters - * in the string one at a time and return their code points, taking - * care to step through the surrogate pairs in UTF-16 encoding - * properly.
- * - * The standard Javascript String's charCodeAt() method only - * returns information about a particular 16-bit character in the - * UTF-16 encoding scheme. - * If the index is pointing to a low- or high-surrogate character, - * it will return a code point of the surrogate character rather - * than the code point of the character - * in the supplementary planes that the two surrogates together - * encode.
- * - * The iterator instance returned has two methods, hasNext() which - * returns true if the iterator has more code points to iterate through, - * and next() which returns the next code point as a number.
- * - * @return {Object} an iterator - * that iterates through all the code points in the string - */ - iterator: function() { - /** - * @constructor - */ - function _iterator (istring) { - this.index = 0; - this.hasNext = function () { - return (this.index < istring.str.length); - }; - this.next = function () { - if (this.index < istring.str.length) { - var num = istring._toCodePoint(this.index); - this.index += ((num > 0xFFFF) ? 2 : 1); - } else { - num = -1; - } - return num; - }; - }; - return new _iterator(this); - }, - - /** - * Return an iterator that will step through all of the characters - * in the string one at a time, taking - * care to step through the surrogate pairs in UTF-16 encoding - * properly.
- * - * The standard Javascript String's charAt() method only - * returns information about a particular 16-bit character in the - * UTF-16 encoding scheme. - * If the index is pointing to a low- or high-surrogate character, - * it will return that surrogate character rather - * than the surrogate pair which represents a character - * in the supplementary planes.
- * - * The iterator instance returned has two methods, hasNext() which - * returns true if the iterator has more characters to iterate through, - * and next() which returns the next character.
- * - * @return {Object} an iterator - * that iterates through all the characters in the string - */ - charIterator: function() { - /** - * @constructor - */ - function _chiterator (istring) { - this.index = 0; - this.hasNext = function () { - return (this.index < istring.str.length); - }; - this.next = function () { - var ch; - if (this.index < istring.str.length) { - ch = istring.str.charAt(this.index); - if (IString._isSurrogate(ch) && - this.index+1 < istring.str.length && - IString._isSurrogate(istring.str.charAt(this.index+1))) { - this.index++; - ch += istring.str.charAt(this.index); - } - this.index++; - } - return ch; - }; - }; - return new _chiterator(this); - }, - - /** - * Return the code point at the given index when the string is viewed - * as an array of code points. If the index is beyond the end of the - * array of code points or if the index is negative, -1 is returned. - * @param {number} index index of the code point - * @return {number} code point of the character at the given index into - * the string - */ - codePointAt: function (index) { - if (index < 0) { - return -1; - } - var count, - it = this.iterator(), - ch; - for (count = index; count >= 0 && it.hasNext(); count--) { - ch = it.next(); - } - return (count < 0) ? ch : -1; - }, - - /** - * Set the locale to use when processing choice formats. The locale - * affects how number classes are interpretted. In some cultures, - * the limit "few" maps to "any integer that ends in the digits 2 to 9" and - * in yet others, "few" maps to "any integer that ends in the digits - * 3 or 4". - * @param {Locale|string} locale locale to use when processing choice - * formats with this string - * @param {boolean=} sync [optional] whether to load the locale data synchronously - * or not - * @param {Object=} loadParams [optional] parameters to pass to the loader function - * @param {function(*)=} onLoad [optional] function to call when the loading is done - */ - setLocale: function (locale, sync, loadParams, onLoad) { - if (typeof(locale) === 'object') { - this.locale = locale; - } else { - this.localeSpec = locale; - this.locale = new Locale(locale); - } - - IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); - }, - - /** - * Return the locale to use when processing choice formats. The locale - * affects how number classes are interpretted. In some cultures, - * the limit "few" maps to "any integer that ends in the digits 2 to 9" and - * in yet others, "few" maps to "any integer that ends in the digits - * 3 or 4". - * @return {string} localespec to use when processing choice - * formats with this string - */ - getLocale: function () { - return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); - }, - - /** - * Return the number of code points in this string. This may be different - * than the number of characters, as the UTF-16 encoding that Javascript - * uses for its basis returns surrogate pairs separately. Two 2-byte - * surrogate characters together make up one character/code point in - * the supplementary character planes. If your string contains no - * characters in the supplementary planes, this method will return the - * same thing as the length() method. - * @return {number} the number of code points in this string - */ - codePointLength: function () { - if (this.cpLength === -1) { - var it = this.iterator(); - this.cpLength = 0; - while (it.hasNext()) { - this.cpLength++; - it.next(); - }; - } - return this.cpLength; - } + result = result.format(params); + + return result.toString(); + }, + + // delegates + /** + * Same as String.toString() + * @return {string} this instance as regular Javascript string + */ + toString: function () { + return this.str.toString(); + }, + + /** + * Same as String.valueOf() + * @return {string} this instance as a regular Javascript string + */ + valueOf: function () { + return this.str.valueOf(); + }, + + /** + * Same as String.charAt() + * @param {number} index the index of the character being sought + * @return {IString} the character at the given index + */ + charAt: function(index) { + return new IString(this.str.charAt(index)); + }, + + /** + * Same as String.charCodeAt(). This only reports on + * 2-byte UCS-2 Unicode values, and does not take into + * account supplementary characters encoded in UTF-16. + * If you would like to take account of those characters, + * use codePointAt() instead. + * @param {number} index the index of the character being sought + * @return {number} the character code of the character at the + * given index in the string + */ + charCodeAt: function(index) { + return this.str.charCodeAt(index); + }, + + /** + * Same as String.concat() + * @param {string} strings strings to concatenate to the current one + * @return {IString} a concatenation of the given strings + */ + concat: function(strings) { + return new IString(this.str.concat(strings)); + }, + + /** + * Same as String.indexOf() + * @param {string} searchValue string to search for + * @param {number} start index into the string to start searching, or + * undefined to search the entire string + * @return {number} index into the string of the string being sought, + * or -1 if the string is not found + */ + indexOf: function(searchValue, start) { + return this.str.indexOf(searchValue, start); + }, + + /** + * Same as String.lastIndexOf() + * @param {string} searchValue string to search for + * @param {number} start index into the string to start searching, or + * undefined to search the entire string + * @return {number} index into the string of the string being sought, + * or -1 if the string is not found + */ + lastIndexOf: function(searchValue, start) { + return this.str.lastIndexOf(searchValue, start); + }, + + /** + * Same as String.match() + * @param {string} regexp the regular expression to match + * @return {Array.
} an array of matches + */ + match: function(regexp) { + return this.str.match(regexp); + }, + + /** + * Same as String.replace() + * @param {string} searchValue a regular expression to search for + * @param {string} newValue the string to replace the matches with + * @return {IString} a new string with all the matches replaced + * with the new value + */ + replace: function(searchValue, newValue) { + return new IString(this.str.replace(searchValue, newValue)); + }, + + /** + * Same as String.search() + * @param {string} regexp the regular expression to search for + * @return {number} position of the match, or -1 for no match + */ + search: function(regexp) { + return this.str.search(regexp); + }, + + /** + * Same as String.slice() + * @param {number} start first character to include in the string + * @param {number} end include all characters up to, but not including + * the end character + * @return {IString} a slice of the current string + */ + slice: function(start, end) { + return new IString(this.str.slice(start, end)); + }, + + /** + * Same as String.split() + * @param {string} separator regular expression to match to find + * separations between the parts of the text + * @param {number} limit maximum number of items in the final + * output array. Any items beyond that limit will be ignored. + * @return {Array. } the parts of the current string split + * by the separator + */ + split: function(separator, limit) { + return this.str.split(separator, limit); + }, + + /** + * Same as String.substr() + * @param {number} start the index of the character that should + * begin the returned substring + * @param {number} length the number of characters to return after + * the start character. + * @return {IString} the requested substring + */ + substr: function(start, length) { + var plat = ilib._getPlatform(); + if (plat === "qt" || plat === "rhino" || plat === "trireme") { + // qt and rhino have a broken implementation of substr(), so + // work around it + if (typeof(length) === "undefined") { + length = this.str.length - start; + } + } + return new IString(this.str.substr(start, length)); + }, + + /** + * Same as String.substring() + * @param {number} from the index of the character that should + * begin the returned substring + * @param {number} to the index where to stop the extraction. If + * omitted, extracts the rest of the string + * @return {IString} the requested substring + */ + substring: function(from, to) { + return this.str.substring(from, to); + }, + + /** + * Same as String.toLowerCase(). Note that this method is + * not locale-sensitive. + * @return {IString} a string with the first character + * lower-cased + */ + toLowerCase: function() { + return this.str.toLowerCase(); + }, + + /** + * Same as String.toUpperCase(). Note that this method is + * not locale-sensitive. Use toLocaleUpperCase() instead + * to get locale-sensitive behaviour. + * @return {IString} a string with the first character + * upper-cased + */ + toUpperCase: function() { + return this.str.toUpperCase(); + }, + + /** + * Convert the character or the surrogate pair at the given + * index into the string to a Unicode UCS-4 code point. + * @protected + * @param {number} index index into the string + * @return {number} code point of the character at the + * given index into the string + */ + _toCodePoint: function (index) { + return IString.toCodePoint(this.str, index); + }, + + /** + * Call the callback with each character in the string one at + * a time, taking care to step through the surrogate pairs in + * the UTF-16 encoding properly. + * + * The standard Javascript String's charAt() method only + * returns a particular 16-bit character in the + * UTF-16 encoding scheme. + * If the index to charAt() is pointing to a low- or + * high-surrogate character, + * it will return the surrogate character rather + * than the the character + * in the supplementary planes that the two surrogates together + * encode. This function will call the callback with the full + * character, making sure to join two + * surrogates into one character in the supplementary planes + * where necessary.
+ * + * @param {function(string)} callback a callback function to call with each + * full character in the current string + */ + forEach: function(callback) { + if (typeof(callback) === 'function') { + var it = this.charIterator(); + while (it.hasNext()) { + callback(it.next()); + } + } + }, + + /** + * Call the callback with each numeric code point in the string one at + * a time, taking care to step through the surrogate pairs in + * the UTF-16 encoding properly.
+ * + * The standard Javascript String's charCodeAt() method only + * returns information about a particular 16-bit character in the + * UTF-16 encoding scheme. + * If the index to charCodeAt() is pointing to a low- or + * high-surrogate character, + * it will return the code point of the surrogate character rather + * than the code point of the character + * in the supplementary planes that the two surrogates together + * encode. This function will call the callback with the full + * code point of each character, making sure to join two + * surrogates into one code point in the supplementary planes.
+ * + * @param {function(string)} callback a callback function to call with each + * code point in the current string + */ + forEachCodePoint: function(callback) { + if (typeof(callback) === 'function') { + var it = this.iterator(); + while (it.hasNext()) { + callback(it.next()); + } + } + }, + + /** + * Return an iterator that will step through all of the characters + * in the string one at a time and return their code points, taking + * care to step through the surrogate pairs in UTF-16 encoding + * properly.
+ * + * The standard Javascript String's charCodeAt() method only + * returns information about a particular 16-bit character in the + * UTF-16 encoding scheme. + * If the index is pointing to a low- or high-surrogate character, + * it will return a code point of the surrogate character rather + * than the code point of the character + * in the supplementary planes that the two surrogates together + * encode.
+ * + * The iterator instance returned has two methods, hasNext() which + * returns true if the iterator has more code points to iterate through, + * and next() which returns the next code point as a number.
+ * + * @return {Object} an iterator + * that iterates through all the code points in the string + */ + iterator: function() { + /** + * @constructor + */ + function _iterator (istring) { + this.index = 0; + this.hasNext = function () { + return (this.index < istring.str.length); + }; + this.next = function () { + if (this.index < istring.str.length) { + var num = istring._toCodePoint(this.index); + this.index += ((num > 0xFFFF) ? 2 : 1); + } else { + num = -1; + } + return num; + }; + }; + return new _iterator(this); + }, + + /** + * Return an iterator that will step through all of the characters + * in the string one at a time, taking + * care to step through the surrogate pairs in UTF-16 encoding + * properly.
+ * + * The standard Javascript String's charAt() method only + * returns information about a particular 16-bit character in the + * UTF-16 encoding scheme. + * If the index is pointing to a low- or high-surrogate character, + * it will return that surrogate character rather + * than the surrogate pair which represents a character + * in the supplementary planes.
+ * + * The iterator instance returned has two methods, hasNext() which + * returns true if the iterator has more characters to iterate through, + * and next() which returns the next character.
+ * + * @return {Object} an iterator + * that iterates through all the characters in the string + */ + charIterator: function() { + /** + * @constructor + */ + function _chiterator (istring) { + this.index = 0; + this.hasNext = function () { + return (this.index < istring.str.length); + }; + this.next = function () { + var ch; + if (this.index < istring.str.length) { + ch = istring.str.charAt(this.index); + if (IString._isSurrogate(ch) && + this.index+1 < istring.str.length && + IString._isSurrogate(istring.str.charAt(this.index+1))) { + this.index++; + ch += istring.str.charAt(this.index); + } + this.index++; + } + return ch; + }; + }; + return new _chiterator(this); + }, + + /** + * Return the code point at the given index when the string is viewed + * as an array of code points. If the index is beyond the end of the + * array of code points or if the index is negative, -1 is returned. + * @param {number} index index of the code point + * @return {number} code point of the character at the given index into + * the string + */ + codePointAt: function (index) { + if (index < 0) { + return -1; + } + var count, + it = this.iterator(), + ch; + for (count = index; count >= 0 && it.hasNext(); count--) { + ch = it.next(); + } + return (count < 0) ? ch : -1; + }, + + /** + * Set the locale to use when processing choice formats. The locale + * affects how number classes are interpretted. In some cultures, + * the limit "few" maps to "any integer that ends in the digits 2 to 9" and + * in yet others, "few" maps to "any integer that ends in the digits + * 3 or 4". + * @param {Locale|string} locale locale to use when processing choice + * formats with this string + * @param {boolean=} sync [optional] whether to load the locale data synchronously + * or not + * @param {Object=} loadParams [optional] parameters to pass to the loader function + * @param {function(*)=} onLoad [optional] function to call when the loading is done + */ + setLocale: function (locale, sync, loadParams, onLoad) { + if (typeof(locale) === 'object') { + this.locale = locale; + } else { + this.localeSpec = locale; + this.locale = new Locale(locale); + } + + IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); + }, + + /** + * Return the locale to use when processing choice formats. The locale + * affects how number classes are interpretted. In some cultures, + * the limit "few" maps to "any integer that ends in the digits 2 to 9" and + * in yet others, "few" maps to "any integer that ends in the digits + * 3 or 4". + * @return {string} localespec to use when processing choice + * formats with this string + */ + getLocale: function () { + return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); + }, + + /** + * Return the number of code points in this string. This may be different + * than the number of characters, as the UTF-16 encoding that Javascript + * uses for its basis returns surrogate pairs separately. Two 2-byte + * surrogate characters together make up one character/code point in + * the supplementary character planes. If your string contains no + * characters in the supplementary planes, this method will return the + * same thing as the length() method. + * @return {number} the number of code points in this string + */ + codePointLength: function () { + if (this.cpLength === -1) { + var it = this.iterator(); + this.cpLength = 0; + while (it.hasNext()) { + this.cpLength++; + it.next(); + }; + } + return this.cpLength; + } }; module.exports = IString; diff --git a/js/lib/IslamicCal.js b/js/lib/IslamicCal.js index bab2fe632d..a68dbd873a 100644 --- a/js/lib/IslamicCal.js +++ b/js/lib/IslamicCal.js @@ -1,6 +1,6 @@ /* * IslamicCal.js - Represent a Islamic calendar object. - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,61 +23,61 @@ var Calendar = require("./Calendar.js"); /** * @class * Construct a new Islamic calendar object. This class encodes information about - * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic - * calendar where the dates are calculated by arithmetic rules. This differs from - * the religious Islamic calendar which is used to mark the beginning of particular - * holidays. The religious calendar depends on the first sighting of the new - * crescent moon to determine the first day of the new month. Because humans and - * weather are both involved, the actual time of sighting varies, so it is not - * really possible to precalculate the religious calendar. Certain groups, such + * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic + * calendar where the dates are calculated by arithmetic rules. This differs from + * the religious Islamic calendar which is used to mark the beginning of particular + * holidays. The religious calendar depends on the first sighting of the new + * crescent moon to determine the first day of the new month. Because humans and + * weather are both involved, the actual time of sighting varies, so it is not + * really possible to precalculate the religious calendar. Certain groups, such * as the Islamic Society of North America, decreed in in 2007 that they will use - * a calendar based on calculations rather than observations to determine the + * a calendar based on calculations rather than observations to determine the * beginning of lunar months, and therefore the dates of holidays.
- * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var IslamicCal = function(options) { - this.type = "islamic"; - + this.type = "islamic"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } }; /** - * the lengths of each month + * the lengths of each month * @private * @const * @type Array.
*/ IslamicCal.monthLengths = [ - 30, /* Muharram */ - 29, /* Saffar */ - 30, /* Rabi'I */ - 29, /* Rabi'II */ - 30, /* Jumada I */ - 29, /* Jumada II */ - 30, /* Rajab */ - 29, /* Sha'ban */ - 30, /* Ramadan */ - 29, /* Shawwal */ - 30, /* Dhu al-Qa'da */ - 29 /* Dhu al-Hijja */ + 30, /* Muharram */ + 29, /* Saffar */ + 30, /* Rabi'I */ + 29, /* Rabi'II */ + 30, /* Jumada I */ + 29, /* Jumada II */ + 30, /* Rajab */ + 29, /* Sha'ban */ + 30, /* Ramadan */ + 29, /* Shawwal */ + 30, /* Dhu al-Qa'da */ + 29 /* Dhu al-Hijja */ ]; /** * Return the number of months in the given year. The number of months in a year varies - * for luni-solar calendars because in some years, an extra month is needed to extend the + * for 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 */ IslamicCal.prototype.getNumMonths = function(year) { - return 12; + return 12; }; /** @@ -90,11 +90,11 @@ IslamicCal.prototype.getNumMonths = function(year) { * @return {number} the number of days within the given month in the given year */ IslamicCal.prototype.getMonLength = function(month, year) { - if (month !== 12) { - return IslamicCal.monthLengths[month-1]; - } else { - return this.isLeapYear(year) ? 30 : 29; - } + if (month !== 12) { + return IslamicCal.monthLengths[month-1]; + } else { + return this.isLeapYear(year) ? 30 : 29; + } }; /** @@ -104,16 +104,16 @@ IslamicCal.prototype.getMonLength = function(month, year) { * @return {boolean} true if the given year is a leap year */ IslamicCal.prototype.isLeapYear = function(year) { - return (MathUtils.mod((14 + 11 * year), 30) < 11); + return (MathUtils.mod((14 + 11 * year), 30) < 11); }; /** * 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 */ IslamicCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/IslamicDate.js b/js/lib/IslamicDate.js index 4d152073bf..bbe5aa7879 100644 --- a/js/lib/IslamicDate.js +++ b/js/lib/IslamicDate.js @@ -1,6 +1,6 @@ /* * IslamicDate.js - Represent a date in the Islamic calendar - * + * * Copyright © 2012-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"); @@ -33,44 +33,44 @@ var IslamicCal = require("./IslamicCal.js"); * @class * Construct a new civil Islamic date object. The constructor can be called * with a params object that can contain the following properties: - * + * *
*
- * + * * If called with another Islamic date argument, the date components of the given * date are copied into the current one.- julianday - the Julian Day to set into this date *
- year - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year *
- month - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. *
- 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 julian 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 the constructor is called with no arguments at all or if none of the - * properties listed above - * from julianday 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 julianday 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 IDate * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date @@ -180,8 +180,8 @@ IslamicDate.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) { @@ -213,30 +213,30 @@ IslamicDate.prototype._init2 = function (params) { }; /** - * the cumulative lengths of each month, for a non-leap year + * the cumulative lengths of each month, for a non-leap year * @private * @const * @type Array.
*/ IslamicDate.cumMonthLengths = [ - 0, /* Muharram */ - 30, /* Saffar */ - 59, /* Rabi'I */ - 89, /* Rabi'II */ - 118, /* Jumada I */ - 148, /* Jumada II */ - 177, /* Rajab */ - 207, /* Sha'ban */ - 236, /* Ramadan */ - 266, /* Shawwal */ - 295, /* Dhu al-Qa'da */ - 325, /* Dhu al-Hijja */ - 354 + 0, /* Muharram */ + 30, /* Saffar */ + 59, /* Rabi'I */ + 89, /* Rabi'II */ + 118, /* Jumada I */ + 148, /* Jumada II */ + 177, /* Rajab */ + 207, /* Sha'ban */ + 236, /* Ramadan */ + 266, /* Shawwal */ + 295, /* Dhu al-Qa'da */ + 325, /* Dhu al-Hijja */ + 354 ]; /** * Number of days difference between RD 0 of the Gregorian calendar and - * RD 0 of the Islamic calendar. + * RD 0 of the Islamic calendar. * @private * @const * @type number @@ -250,17 +250,17 @@ IslamicDate.GregorianDiff = 227015; * @returns {RataDie} the new RD instance for the given params */ IslamicDate.prototype.newRd = function (params) { - return new IslamicRataDie(params); + return new IslamicRataDie(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 */ IslamicDate.prototype._calcYear = function(rd) { - return Math.floor((30 * rd + 10646) / 10631); + return Math.floor((30 * rd + 10646) / 10631); }; /** @@ -268,110 +268,110 @@ IslamicDate.prototype._calcYear = function(rd) { * @protected */ IslamicDate.prototype._calcDateComponents = function () { - var remainder, - rd = this.rd.getRataDie(); - - this.year = this._calcYear(rd); - - if (typeof(this.offset) === "undefined") { - this.year = this._calcYear(rd); - - // 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("IslamicDate.calcComponent: calculating for rd " + rd); - //console.log("IslamicDate.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("IslamicDate.calcComponent: remainder is " + remainder); - - this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); - remainder -= IslamicDate.cumMonthLengths[this.month-1]; - - //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); - - this.day = Math.floor(remainder); - remainder -= this.day; - - //console.log("IslamicDate.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") { + this.year = this._calcYear(rd); + + // 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("IslamicDate.calcComponent: calculating for rd " + rd); + //console.log("IslamicDate.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("IslamicDate.calcComponent: remainder is " + remainder); + + this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); + remainder -= IslamicDate.cumMonthLengths[this.month-1]; + + //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); + + this.day = Math.floor(remainder); + remainder -= this.day; + + //console.log("IslamicDate.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 */ IslamicDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd-2, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd-2, 7); }; /** - * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to - * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and + * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to + * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and * Dhu al-Hijja 29 is 354. * @return {number} the ordinal day of the year */ IslamicDate.prototype.getDayOfYear = function() { - return IslamicDate.cumMonthLengths[this.month-1] + this.day; + return IslamicDate.cumMonthLengths[this.month-1] + this.day; }; /** - * Return the era for this date as a number. The value for the era for Islamic - * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". + * Return the era for this date as a number. The value for the era for Islamic + * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". * Islamic era dates are any date after Muharran 1, 1, which is the same as - * July 16, 622 CE in the Gregorian calendar. - * - * @return {number} 1 if this date is in the common era, -1 if it is before the - * common era + * July 16, 622 CE in the Gregorian calendar. + * + * @return {number} 1 if this date is in the common era, -1 if it is before the + * common era */ IslamicDate.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 */ IslamicDate.prototype.getCalendar = function() { - return "islamic"; + return "islamic"; }; //register with the factory method diff --git a/js/lib/IslamicRataDie.js b/js/lib/IslamicRataDie.js index 5c362552a4..b2b7b69ba3 100644 --- a/js/lib/IslamicRataDie.js +++ b/js/lib/IslamicRataDie.js @@ -1,6 +1,6 @@ /* * IslamicRataDie.js - Represent an RD date in the Islamic calendar - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,56 +22,56 @@ var IslamicCal = require("./IslamicCal.js"); /** * @class - * Construct a new Islamic RD date number object. The constructor parameters can + * Construct a new Islamic RD date number object. The constructor parameters can * contain any of the following properties: - * + * * - *
* * If the constructor is called with another Islamic date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * + * *
- date - use the given intrinsic Javascript date to initialize this one. *
- * - * 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 Islamic RD date */ var IslamicRataDie = function(params) { - this.cal = params && params.cal || new IslamicCal(); - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new IslamicCal(); + this.rd = NaN; + RataDie.call(this, params); }; IslamicRataDie.prototype = new RataDie(); @@ -80,7 +80,7 @@ IslamicRataDie.prototype.constructor = IslamicRataDie; /** * The difference between a zero Julian day and the first Islamic date - * of Friday, July 16, 622 CE Julian. + * of Friday, July 16, 622 CE Julian. * @private * @type number */ @@ -94,22 +94,22 @@ IslamicRataDie.prototype.epoch = 1948439.5; * @param {Object} date the date components to calculate the RD from */ IslamicRataDie.prototype._setDateComponents = function(date) { - var days = (date.year - 1) * 354 + - Math.ceil(29.5 * (date.month - 1)) + - date.day + - Math.floor((3 + 11 * date.year) / 30) - 1; - var time = (date.hour * 3600000 + - date.minute * 60000 + - date.second * 1000 + - date.millisecond) / - 86400000; - - //console.log("getRataDie: converting " + JSON.stringify(date)); - //console.log("getRataDie: days is " + days); - //console.log("getRataDie: time is " + time); - //console.log("getRataDie: rd is " + (days + time)); + var days = (date.year - 1) * 354 + + Math.ceil(29.5 * (date.month - 1)) + + date.day + + Math.floor((3 + 11 * date.year) / 30) - 1; + var time = (date.hour * 3600000 + + date.minute * 60000 + + date.second * 1000 + + date.millisecond) / + 86400000; - this.rd = days + time; + //console.log("getRataDie: converting " + JSON.stringify(date)); + //console.log("getRataDie: days is " + days); + //console.log("getRataDie: time is " + time); + //console.log("getRataDie: rd is " + (days + time)); + + this.rd = days + time; }; - + module.exports = IslamicRataDie; \ No newline at end of file diff --git a/js/lib/JSUtils.js b/js/lib/JSUtils.js index c843fb3e1f..f09508516e 100644 --- a/js/lib/JSUtils.js +++ b/js/lib/JSUtils.js @@ -32,14 +32,14 @@ var JSUtils = {}; * @param {Object} target the target object to copy properties into */ JSUtils.shallowCopy = function (source, target) { - var prop = undefined; - if (source && target) { - for (prop in source) { - if (prop !== undefined && typeof(source[prop]) !== 'undefined') { - target[prop] = source[prop]; - } - } - } + var prop = undefined; + if (source && target) { + for (prop in source) { + if (prop !== undefined && typeof(source[prop]) !== 'undefined') { + target[prop] = source[prop]; + } + } + } }; /** @@ -51,19 +51,19 @@ JSUtils.shallowCopy = function (source, target) { * @return {Object} a reference to the the "to" object */ JSUtils.deepCopy = function(from, to) { - var prop; + var prop; - for (prop in from) { - if (prop) { - if (typeof(from[prop]) === 'object') { - to[prop] = {}; - JSUtils.deepCopy(from[prop], to[prop]); - } else { - to[prop] = from[prop]; - } - } - } - return to; + for (prop in from) { + if (prop) { + if (typeof(from[prop]) === 'object') { + to[prop] = {}; + JSUtils.deepCopy(from[prop], to[prop]); + } else { + to[prop] = from[prop]; + } + } + } + return to; }; /** @@ -77,16 +77,16 @@ JSUtils.deepCopy = function(from, to) { * @return {string} the source string where each character is mapped to alternate characters */ JSUtils.mapString = function (str, map) { - var mapped = ""; - if (map && str) { - for (var i = 0; i < str.length; i++) { - var c = str.charAt(i); // TODO use a char iterator? - mapped += map[c] || c; - } - } else { - mapped = str; - } - return mapped; + var mapped = ""; + if (map && str) { + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); // TODO use a char iterator? + mapped += map[c] || c; + } + } else { + mapped = str; + } + return mapped; }; /** @@ -103,19 +103,19 @@ JSUtils.mapString = function (str, map) { * @return {number} index of the object in the array, or -1 if it is not in the array. */ JSUtils.indexOf = function(array, obj) { - if (!array || !obj) { - return -1; - } - if (typeof(array.indexOf) === 'function') { - return array.indexOf(obj); - } else { - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { - return i; - } - } - return -1; - } + if (!array || !obj) { + return -1; + } + if (typeof(array.indexOf) === 'function') { + return array.indexOf(obj); + } else { + for (var i = 0; i < array.length; i++) { + if (array[i] === obj) { + return i; + } + } + return -1; + } }; /** @@ -128,17 +128,17 @@ JSUtils.indexOf = function(array, obj) { * Default is false. */ JSUtils.pad = function (str, length, right) { - if (typeof(str) !== 'string') { - str = "" + str; - } - var start = 0; - // take care of negative numbers - if (str.charAt(0) === '-') { - start++; - } - return (str.length >= length+start) ? str : - (right ? str + JSUtils.pad.zeros.substring(0,length-str.length+start) : - str.substring(0, start) + JSUtils.pad.zeros.substring(0,length-str.length+start) + str.substring(start)); + if (typeof(str) !== 'string') { + str = "" + str; + } + var start = 0; + // take care of negative numbers + if (str.charAt(0) === '-') { + start++; + } + return (str.length >= length+start) ? str : + (right ? str + JSUtils.pad.zeros.substring(0,length-str.length+start) : + str.substring(0, start) + JSUtils.pad.zeros.substring(0,length-str.length+start) + str.substring(start)); }; /** @private */ @@ -155,18 +155,18 @@ JSUtils.pad.zeros = "00000000000000000000000000000000"; * Unicode characters in the input string */ JSUtils.toHexString = function(string, limit) { - var i, - result = "", - lim = (limit && limit < 9) ? limit : 4; + var i, + result = "", + lim = (limit && limit < 9) ? limit : 4; - if (!string) { - return ""; - } - for (i = 0; i < string.length; i++) { - var ch = string.charCodeAt(i).toString(16); - result += JSUtils.pad(ch, lim); - } - return result.toUpperCase(); + if (!string) { + return ""; + } + for (i = 0; i < string.length; i++) { + var ch = string.charCodeAt(i).toString(16); + result += JSUtils.pad(ch, lim); + } + return result.toUpperCase(); }; /** @@ -178,10 +178,10 @@ JSUtils.toHexString = function(string, limit) { * and false otherwise */ JSUtils.isDate = function(object) { - if (typeof(object) === 'object') { - return Object.prototype.toString.call(object) === '[object Date]'; - } - return false; + if (typeof(object) === 'object') { + return Object.prototype.toString.call(object) === '[object Date]'; + } + return false; }; /** @@ -204,34 +204,34 @@ JSUtils.isDate = function(object) { * @return {Object} the merged object */ JSUtils.merge = function (object1, object2, replace, name1, name2) { - var prop = undefined, - newObj = {}; - for (prop in object1) { - if (prop && typeof(object1[prop]) !== 'undefined') { - newObj[prop] = object1[prop]; - } - } - for (prop in object2) { - if (prop && typeof(object2[prop]) !== 'undefined') { - if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { - if (typeof(replace) !== 'boolean' || !replace) { - newObj[prop] = [].concat(object1[prop]); - newObj[prop] = newObj[prop].concat(object2[prop]); - } else { - newObj[prop] = object2[prop]; - } - } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { - newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); - } else { - // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily - if (name1 && name2 && newObj[prop] == object2[prop]) { - console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); - } - newObj[prop] = object2[prop]; - } - } - } - return newObj; + var prop = undefined, + newObj = {}; + for (prop in object1) { + if (prop && typeof(object1[prop]) !== 'undefined') { + newObj[prop] = object1[prop]; + } + } + for (prop in object2) { + if (prop && typeof(object2[prop]) !== 'undefined') { + if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { + if (typeof(replace) !== 'boolean' || !replace) { + newObj[prop] = [].concat(object1[prop]); + newObj[prop] = newObj[prop].concat(object2[prop]); + } else { + newObj[prop] = object2[prop]; + } + } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { + newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); + } else { + // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily + if (name1 && name2 && newObj[prop] == object2[prop]) { + console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); + } + newObj[prop] = object2[prop]; + } + } + } + return newObj; }; /** @@ -243,74 +243,74 @@ JSUtils.merge = function (object1, object2, replace, name1, name2) { * @return {boolean} true if the given object has no properties, false otherwise */ JSUtils.isEmpty = function (obj) { - var prop = undefined; + var prop = undefined; - if (!obj) { - return true; - } + if (!obj) { + return true; + } - for (prop in obj) { - if (prop && typeof(obj[prop]) !== 'undefined') { - return false; - } - } - return true; + for (prop in obj) { + if (prop && typeof(obj[prop]) !== 'undefined') { + return false; + } + } + return true; }; /** * @static */ JSUtils.hashCode = function(obj) { - var hash = 0; + var hash = 0; - function addHash(hash, newValue) { - // co-prime numbers creates a nicely distributed hash - hash *= 65543; - hash += newValue; - hash %= 2147483647; - return hash; - } + function addHash(hash, newValue) { + // co-prime numbers creates a nicely distributed hash + hash *= 65543; + hash += newValue; + hash %= 2147483647; + return hash; + } - function stringHash(str) { - var hash = 0; - for (var i = 0; i < str.length; i++) { - hash = addHash(hash, str.charCodeAt(i)); - } - return hash; - } + function stringHash(str) { + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = addHash(hash, str.charCodeAt(i)); + } + return hash; + } - switch (typeof(obj)) { - case 'undefined': - hash = 0; - break; - case 'string': - hash = stringHash(obj); - break; - case 'function': - case 'number': - case 'xml': - hash = stringHash(String(obj)); - break; - case 'boolean': - hash = obj ? 1 : 0; - break; - case 'object': - var props = []; - for (var p in obj) { - if (obj.hasOwnProperty(p)) { - props.push(p); - } - } - // make sure the order of the properties doesn't matter - props.sort(); - for (var i = 0; i < props.length; i++) { - hash = addHash(hash, stringHash(props[i])); - hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); - } - break; - } + switch (typeof(obj)) { + case 'undefined': + hash = 0; + break; + case 'string': + hash = stringHash(obj); + break; + case 'function': + case 'number': + case 'xml': + hash = stringHash(String(obj)); + break; + case 'boolean': + hash = obj ? 1 : 0; + break; + case 'object': + var props = []; + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + props.push(p); + } + } + // make sure the order of the properties doesn't matter + props.sort(); + for (var i = 0; i < props.length; i++) { + hash = addHash(hash, stringHash(props[i])); + hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); + } + break; + } - return hash; + return hash; }; /** diff --git a/js/lib/JulianCal.js b/js/lib/JulianCal.js index f7f6cea6d5..34f606a80d 100644 --- a/js/lib/JulianCal.js +++ b/js/lib/JulianCal.js @@ -1,6 +1,6 @@ /* * JulianCal.js - Represent a Julian calendar object. - * + * * Copyright © 2012-2017, 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 Julian calendar object. This class encodes information about * a Julian calendar.
- * + * * @param {Object=} options Options governing the construction of this instance * @constructor * @extends Calendar */ var JulianCal = function(options) { - this.type = "julian"; - + this.type = "julian"; + if (options && typeof(options.onLoad) === "function") { options.onLoad(this); } @@ -39,91 +39,91 @@ var JulianCal = function(options) { /* the lengths of each month */ JulianCal.monthLengths = [ - 31, /* Jan */ - 28, /* Feb */ - 31, /* Mar */ - 30, /* Apr */ - 31, /* May */ - 30, /* Jun */ - 31, /* Jul */ - 31, /* Aug */ - 30, /* Sep */ - 31, /* Oct */ - 30, /* Nov */ - 31 /* Dec */ + 31, /* Jan */ + 28, /* Feb */ + 31, /* Mar */ + 30, /* Apr */ + 31, /* May */ + 30, /* Jun */ + 31, /* Jul */ + 31, /* Aug */ + 30, /* Sep */ + 31, /* Oct */ + 30, /* Nov */ + 31 /* Dec */ ]; /** - * the cumulative lengths of each month, for a non-leap year + * the cumulative lengths of each month, for a non-leap year * @private * @const * @type Array.
*/ JulianCal.cumMonthLengths = [ 0, /* Jan */ - 31, /* Feb */ - 59, /* Mar */ - 90, /* Apr */ - 120, /* May */ - 151, /* Jun */ - 181, /* Jul */ - 212, /* Aug */ - 243, /* Sep */ - 273, /* Oct */ - 304, /* Nov */ - 334, /* Dec */ - 365 + 31, /* Feb */ + 59, /* Mar */ + 90, /* Apr */ + 120, /* May */ + 151, /* Jun */ + 181, /* Jul */ + 212, /* Aug */ + 243, /* Sep */ + 273, /* Oct */ + 304, /* Nov */ + 334, /* Dec */ + 365 ]; /** - * the cumulative lengths of each month, for a leap year + * the cumulative lengths of each month, for a leap year * @private * @const * @type Array. */ JulianCal.cumMonthLengthsLeap = [ - 0, /* Jan */ - 31, /* Feb */ - 60, /* Mar */ - 91, /* Apr */ - 121, /* May */ - 152, /* Jun */ - 182, /* Jul */ - 213, /* Aug */ - 244, /* Sep */ - 274, /* Oct */ - 305, /* Nov */ - 335, /* Dec */ - 366 + 0, /* Jan */ + 31, /* Feb */ + 60, /* Mar */ + 91, /* Apr */ + 121, /* May */ + 152, /* Jun */ + 182, /* Jul */ + 213, /* Aug */ + 244, /* Sep */ + 274, /* Oct */ + 305, /* Nov */ + 335, /* Dec */ + 366 ]; /** * Return the number of months in the given year. The number of months in a year varies - * for lunar calendars because in some years, an extra month is needed to extend the + * for lunar 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=Jaunary, 2=February, etc. until 12=December. - * + * * @param {number} year a year for which the number of months is sought */ JulianCal.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 */ JulianCal.prototype.getMonLength = function(month, year) { - if (month !== 2 || !this.isLeapYear(year)) { - return JulianCal.monthLengths[month-1]; - } else { - return 29; - } + if (month !== 2 || !this.isLeapYear(year)) { + return JulianCal.monthLengths[month-1]; + } else { + return 29; + } }; /** @@ -133,17 +133,17 @@ JulianCal.prototype.getMonLength = function(month, year) { * @return {boolean} true if the given year is a leap year */ JulianCal.prototype.isLeapYear = function(year) { - var y = (typeof(year) === 'number' ? year : year.year); - return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); + var y = (typeof(year) === 'number' ? year : year.year); + return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); }; /** * 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 */ JulianCal.prototype.getType = function() { - return this.type; + return this.type; }; diff --git a/js/lib/JulianDate.js b/js/lib/JulianDate.js index cbf3ee922e..3c32ba3606 100644 --- a/js/lib/JulianDate.js +++ b/js/lib/JulianDate.js @@ -1,6 +1,6 @@ /* * JulianDate.js - Represent a date in the Julian calendar - * + * * Copyright © 2012-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"); @@ -33,91 +33,91 @@ var JulianCal = require("./JulianCal.js"); * @class * Construct a new date object for the Julian Calendar. The constructor can be called * with a parameter object that contains any of the following properties: - * + * * - *
- * - * NB. The Julian Day - * (JulianDay) object is a different object than a + * + * NB. The Julian Day + * (JulianDay) object is a different object than a * date in - * the Julian calendar and the two are not to be confused. The Julian Day - * object represents time as a number of whole and fractional days since the - * beginning of the epoch, whereas a date in the Julian + * the Julian calendar- 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 except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero + *
- year - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero * year which doesn't exist in the Julian calendar *
- 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 - *
- locale - the TimeZone instance or time zone name as a string + *
- locale - the TimeZone instance or time zone name as a string * of this julian 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 Julian 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 IDate * @param {Object=} params parameters that govern the settings and behaviour of this Julian date */ var JulianDate = function(params) { - this.cal = new JulianCal(); - - 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, { - sync: params.sync, - loadParams: params.loadParams, - onLoad: ilib.bind(this, function(li) { - this.li = li; - this.timezone = li.getTimeZone(); - this._init(params); - }) - }); - } else { - this.timezone = "local"; - this._init(params); - } - } else { - this._init(params); - } + this.cal = new JulianCal(); + + 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, { + sync: params.sync, + loadParams: params.loadParams, + onLoad: ilib.bind(this, function(li) { + this.li = li; + this.timezone = li.getTimeZone(); + this._init(params); + }) + }); + } else { + this.timezone = "local"; + this._init(params); + } + } else { + this._init(params); + } }; @@ -188,8 +188,8 @@ JulianDate.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) { @@ -227,19 +227,19 @@ JulianDate.prototype._init2 = function (params) { * @returns {RataDie} the new RD instance for the given params */ JulianDate.prototype.newRd = function (params) { - return new JulianRataDie(params); + return new JulianRataDie(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 */ JulianDate.prototype._calcYear = function(rd) { - var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); - - return (year <= 0) ? year - 1 : year; + var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); + + return (year <= 0) ? year - 1 : year; }; /** @@ -247,81 +247,81 @@ JulianDate.prototype._calcYear = function(rd) { * @protected */ JulianDate.prototype._calcDateComponents = function () { - var remainder, - cumulative, - rd = this.rd.getRataDie(); - - this.year = this._calcYear(rd); - - if (typeof(this.offset) === "undefined") { - this.year = this._calcYear(rd); - - // 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); - } - - var jan1 = this.newRd({ - year: this.year, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - millisecond: 0 - }); - remainder = rd + 1 - jan1.getRataDie(); - - cumulative = this.cal.isLeapYear(this.year) ? - JulianCal.cumMonthLengthsLeap : - JulianCal.cumMonthLengths; - - this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); - remainder = remainder - cumulative[this.month-1]; - - this.day = Math.floor(remainder); - remainder -= this.day; - // 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, + cumulative, + rd = this.rd.getRataDie(); + + this.year = this._calcYear(rd); + + if (typeof(this.offset) === "undefined") { + this.year = this._calcYear(rd); + + // 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); + } + + var jan1 = this.newRd({ + year: this.year, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0 + }); + remainder = rd + 1 - jan1.getRataDie(); + + cumulative = this.cal.isLeapYear(this.year) ? + JulianCal.cumMonthLengthsLeap : + JulianCal.cumMonthLengths; + + this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); + remainder = remainder - cumulative[this.month-1]; + + this.day = Math.floor(remainder); + remainder -= this.day; + // 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 */ JulianDate.prototype.getDayOfWeek = function() { - var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); - return MathUtils.mod(rd-2, 7); + var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); + return MathUtils.mod(rd-2, 7); }; /** * Return the name of the calendar that governs this date. - * + * * @return {string} a string giving the name of the calendar */ JulianDate.prototype.getCalendar = function() { - return "julian"; + return "julian"; }; //register with the factory method diff --git a/js/lib/JulianDay.js b/js/lib/JulianDay.js index 6fba1ada7b..438555fa30 100644 --- a/js/lib/JulianDay.js +++ b/js/lib/JulianDay.js @@ -1,6 +1,6 @@ /* * JulianDay.js - A Julian Day object. - * + * * Copyright © 2012-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,94 +20,94 @@ /** * @class * A Julian Day class. A Julian Day is a date based on the Julian Day count - * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. + * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. * Do not confuse it with a date in the Julian calendar, which it has very * little in common with. The naming is unfortunately close, and comes from history.
- * - * + * + * * @constructor - * @param {number} num the Julian Day expressed as a floating point number + * @param {number} num the Julian Day expressed as a floating point number */ var JulianDay = function(num) { - this.jd = num; - this.days = Math.floor(this.jd); - this.frac = num - this.days; + this.jd = num; + this.days = Math.floor(this.jd); + this.frac = num - this.days; }; JulianDay.prototype = { - /** - * Return the integral portion of this Julian Day instance. This corresponds to - * the number of days since the beginning of the epoch. - * - * @return {number} the integral portion of this Julian Day - */ - getDays: function() { - return this.days; - }, - - /** - * Set the date of this Julian Day instance. - * - * @param {number} days the julian date expressed as a floating point number - */ - setDays: function(days) { - this.days = Math.floor(days); - this.jd = this.days + this.frac; - }, - - /** - * Return the fractional portion of this Julian Day instance. This portion - * corresponds to the time of day for the instance. - */ - getDayFraction: function() { - return this.frac; - }, - - /** - * Set the fractional part of the Julian Day. The fractional part represents - * the portion of a fully day. Julian dates start at noon, and proceed until - * noon of the next day. That would mean midnight is represented as a fractional - * part of 0.5. - * - * @param {number} fraction The fractional part of the Julian date - */ - setDayFraction: function(fraction) { - var t = Math.floor(fraction); - this.frac = fraction - t; - this.jd = this.days + this.frac; - }, - - /** - * Return the Julian Day expressed as a floating point number. - * @return {number} the Julian Day as a number - */ - getDate: function () { - return this.jd; - }, - - /** - * Set the date of this Julian Day instance. - * - * @param {number} num the numeric Julian Day to set into this instance - */ - setDate: function (num) { - this.jd = num; - }, - - /** - * Add an offset to the current date instance. The offset should be expressed in - * terms of Julian days. That is, each integral unit represents one day of time, and - * fractional part represents a fraction of a regular 24-hour day. - * - * @param {number} offset an amount to add (or subtract) to the current result instance. - */ - addDate: function(offset) { - if (typeof(offset) === 'number') { - this.jd += offset; - this.days = Math.floor(this.jd); - this.frac = this.jd - this.days; - } - } + /** + * Return the integral portion of this Julian Day instance. This corresponds to + * the number of days since the beginning of the epoch. + * + * @return {number} the integral portion of this Julian Day + */ + getDays: function() { + return this.days; + }, + + /** + * Set the date of this Julian Day instance. + * + * @param {number} days the julian date expressed as a floating point number + */ + setDays: function(days) { + this.days = Math.floor(days); + this.jd = this.days + this.frac; + }, + + /** + * Return the fractional portion of this Julian Day instance. This portion + * corresponds to the time of day for the instance. + */ + getDayFraction: function() { + return this.frac; + }, + + /** + * Set the fractional part of the Julian Day. The fractional part represents + * the portion of a fully day. Julian dates start at noon, and proceed until + * noon of the next day. That would mean midnight is represented as a fractional + * part of 0.5. + * + * @param {number} fraction The fractional part of the Julian date + */ + setDayFraction: function(fraction) { + var t = Math.floor(fraction); + this.frac = fraction - t; + this.jd = this.days + this.frac; + }, + + /** + * Return the Julian Day expressed as a floating point number. + * @return {number} the Julian Day as a number + */ + getDate: function () { + return this.jd; + }, + + /** + * Set the date of this Julian Day instance. + * + * @param {number} num the numeric Julian Day to set into this instance + */ + setDate: function (num) { + this.jd = num; + }, + + /** + * Add an offset to the current date instance. The offset should be expressed in + * terms of Julian days. That is, each integral unit represents one day of time, and + * fractional part represents a fraction of a regular 24-hour day. + * + * @param {number} offset an amount to add (or subtract) to the current result instance. + */ + addDate: function(offset) { + if (typeof(offset) === 'number') { + this.jd += offset; + this.days = Math.floor(this.jd); + this.frac = this.jd - this.days; + } + } }; module.exports = JulianDay; diff --git a/js/lib/JulianRataDie.js b/js/lib/JulianRataDie.js index 15fa76ff7c..511056fc97 100644 --- a/js/lib/JulianRataDie.js +++ b/js/lib/JulianRataDie.js @@ -1,6 +1,6 @@ /* * JulianDate.js - Represent a date in the Julian calendar - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,56 +22,56 @@ var JulianCal = require("./JulianCal.js"); /** * @class - * Construct a new Julian RD date number object. The constructor parameters can + * Construct a new Julian RD date number object. The constructor parameters can * contain any of the following properties: - * + * *
- *
* * If the constructor is called with another Julian date instance instead of * a parameter block, the other instance acts as a parameter block and its * settings are copied into the current instance.- 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 - * + * *
- date - use the given intrinsic Javascript date to initialize this one. *
- * - * 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 Julian RD date */ var JulianRataDie = function(params) { - this.cal = params && params.cal || new JulianCal(); - this.rd = NaN; - RataDie.call(this, params); + this.cal = params && params.cal || new JulianCal(); + this.rd = NaN; + RataDie.call(this, params); }; JulianRataDie.prototype = new RataDie(); @@ -80,7 +80,7 @@ JulianRataDie.prototype.constructor = JulianRataDie; /** * The difference between a zero Julian day and the first Julian date - * of Friday, July 16, 622 CE Julian. + * of Friday, July 16, 622 CE Julian. * @private * @type number */ @@ -89,31 +89,31 @@ JulianRataDie.prototype.epoch = 1721422.5; /** * Calculate the Rata Die (fixed day) number of the given date from the * date components. - * + * * @protected * @param {Object} date the date components to calculate the RD from */ JulianRataDie.prototype._setDateComponents = function(date) { - var year = date.year + ((date.year < 0) ? 1 : 0); - var years = 365 * (year - 1) + Math.floor((year-1)/4); - var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + - date.day + - (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); - var rdtime = (date.hour * 3600000 + - date.minute * 60000 + - date.second * 1000 + - date.millisecond) / - 86400000; - - /* - console.log("calcRataDie: converting " + JSON.stringify(parts)); - console.log("getRataDie: year is " + years); - console.log("getRataDie: day in year is " + dayInYear); - console.log("getRataDie: rdtime is " + rdtime); - console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); - */ - - this.rd = years + dayInYear + rdtime; + var year = date.year + ((date.year < 0) ? 1 : 0); + var years = 365 * (year - 1) + Math.floor((year-1)/4); + var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + + date.day + + (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); + var rdtime = (date.hour * 3600000 + + date.minute * 60000 + + date.second * 1000 + + date.millisecond) / + 86400000; + + /* + console.log("calcRataDie: converting " + JSON.stringify(parts)); + console.log("getRataDie: year is " + years); + console.log("getRataDie: day in year is " + dayInYear); + console.log("getRataDie: rdtime is " + rdtime); + console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); + */ + + this.rd = years + dayInYear + rdtime; }; module.exports = JulianRataDie; \ No newline at end of file diff --git a/js/lib/ListFmt.js b/js/lib/ListFmt.js index d5159bf12f..674cb08629 100644 --- a/js/lib/ListFmt.js +++ b/js/lib/ListFmt.js @@ -1,6 +1,6 @@ /* * ListFmt.js - Represent a list formatter. - * + * * Copyright © 2017-2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,50 +20,50 @@ // !data list -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); /** * @class - * Create a new list formatter object that formats lists of items according to + * Create a new list formatter object that formats lists of items according to * the options.
- * + * * The options object can contain zero or more of the following parameters: * *
- *
@@ -72,54 +72,54 @@ var Locale = require("./Locale.js"); * @param {Object} options properties that control how this formatter behaves */ var ListFmt = function(options) { - this.locale = new Locale(); - this.sync = true; - this.style = "standard"; - this.length = "short"; - this.loadParams = {}; - - if (options) { - if (options.locale) { - this.locale = options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - this.sync = !!options.sync; - } - - if (options.length) { - this.length = options.length; - } - - if (options.loadParams) { - this.loadParams = options.loadParams; - } - - if (options.style) { - this.style = options.style; - } - } - - Utils.loadData({ - name: "list.json", - object: "ListFmt", - locale: this.locale, - sync: this.sync, - loadParams: this.loadParams, - callback: ilib.bind(this, function (fmtdata) { - this.fmtdata = fmtdata; - - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + this.locale = new Locale(); + this.sync = true; + this.style = "standard"; + this.length = "short"; + this.loadParams = {}; + + if (options) { + if (options.locale) { + this.locale = options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + this.sync = !!options.sync; + } + + if (options.length) { + this.length = options.length; + } + + if (options.loadParams) { + this.loadParams = options.loadParams; + } + + if (options.style) { + this.style = options.style; + } + } + + Utils.loadData({ + name: "list.json", + object: "ListFmt", + locale: this.locale, + sync: this.sync, + loadParams: this.loadParams, + callback: ilib.bind(this, function (fmtdata) { + this.fmtdata = fmtdata; + + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; /** - * Format a list of strings as grammatical text that is appropriate + * Format a list of strings as grammatical text that is appropriate * for the locale of this formatter. - * + * * @param {Array.- locale locale to use to format this list, or undefined to use the + *
- locale locale to use to format this list, or undefined to use the * default locale - * - *
- 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 - *
- medium - *
- long + *
- short + *
- medium + *
- long *
- full *
- style the name of style to use to format the list, or undefined + *
- style the name of style to use to format the list, or undefined * to use the default "standard" style. another style option is "units". * - *
- 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. *
} items an array of strings to format in * order that you would like them to appear * @returns {string} a string containing the list of items that @@ -127,53 +127,53 @@ var ListFmt = function(options) { */ ListFmt.prototype.format = function(items) { - if (!items || (!ilib.isArray(items))) { - return ""; - } - - var itemCount = items.length; - var fmtTemplate, formattedList; - var startFmt, middleFmt, endFmt; - var i; - - fmtTemplate = this.fmtdata[this.style][this.length] || this.fmtdata[this.style]; - startFmt = fmtTemplate["start"]; - middleFmt = fmtTemplate["middle"]; - endFmt = fmtTemplate["end"]; - - if (itemCount === 0) { - return ""; - } - else if (itemCount === 1) { - formattedList = items.toString(); - - } else if ( itemCount === 2) { - fmtTemplate = fmtTemplate["2"]; - formattedList = fmtTemplate.replace("{0}", items[0]).replace("{1}", items[1]); - - } else { - for(i = itemCount; i >= 0 ; i--){ - if (i == itemCount) { - formattedList = endFmt.replace("{0}", items[itemCount-2]).replace("{1}", items[itemCount-1]); - i = i-2; - } else if (i == 0) { - formattedList = startFmt.replace("{0}",items[i]).replace("{1}", formattedList); - } - else { - formattedList = middleFmt.replace("{0}",items[i]).replace("{1}", formattedList); - } - } - } - return formattedList; + if (!items || (!ilib.isArray(items))) { + return ""; + } + + var itemCount = items.length; + var fmtTemplate, formattedList; + var startFmt, middleFmt, endFmt; + var i; + + fmtTemplate = this.fmtdata[this.style][this.length] || this.fmtdata[this.style]; + startFmt = fmtTemplate["start"]; + middleFmt = fmtTemplate["middle"]; + endFmt = fmtTemplate["end"]; + + if (itemCount === 0) { + return ""; + } + else if (itemCount === 1) { + formattedList = items.toString(); + + } else if ( itemCount === 2) { + fmtTemplate = fmtTemplate["2"]; + formattedList = fmtTemplate.replace("{0}", items[0]).replace("{1}", items[1]); + + } else { + for(i = itemCount; i >= 0 ; i--){ + if (i == itemCount) { + formattedList = endFmt.replace("{0}", items[itemCount-2]).replace("{1}", items[itemCount-1]); + i = i-2; + } else if (i == 0) { + formattedList = startFmt.replace("{0}",items[i]).replace("{1}", formattedList); + } + else { + formattedList = middleFmt.replace("{0}",items[i]).replace("{1}", formattedList); + } + } + } + return formattedList; }; /** * Return the locale of this formatter. - * + * * @returns {string} the locale of this formatter */ ListFmt.prototype.getLocale = function() { - return this.locale.getSpec(); + return this.locale.getSpec(); }; /** @@ -181,7 +181,7 @@ ListFmt.prototype.getLocale = function() { * @return {string} the style of names returned by this formatter */ ListFmt.prototype.getStyle = function() { - return this.style; + return this.style; }; module.exports = ListFmt; \ No newline at end of file diff --git a/js/lib/Loader.js b/js/lib/Loader.js index 3ba32dcd64..01f56c624b 100644 --- a/js/lib/Loader.js +++ b/js/lib/Loader.js @@ -1,6 +1,6 @@ /* * Loader.js - shared loader implementation - * + * * Copyright © 2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,18 +20,18 @@ var Path = require("./Path.js"); var ilib = require("./ilib.js"); -/** +/** * @class * Superclass of the loader classes that contains shared functionality. - * + * * @private * @constructor */ var Loader = function() { - // console.log("new Loader instance"); + // console.log("new Loader instance"); - this.protocol = "file://"; - this.includePath = []; + this.protocol = "file://"; + this.includePath = []; }; Loader.prototype = new ilib.Loader(); @@ -41,132 +41,134 @@ Loader.prototype.constructor = Loader; Loader.prototype._loadFile = function (pathname, sync, cb) {}; Loader.prototype._exists = function(dir, file) { - var fullpath = Path.normalize(Path.join(dir, file)); - if (this.protocol !== "http://") { - var text = this._loadFile(fullpath, true); - if (text) { - this.includePath.push(dir); - } - } else { - // put the dir on the list now assuming it exists, and check for its availability - // later so we can avoid the 404 errors eventually - this.includePath.push(dir); - this._loadFile(fullpath, false, ilib.bind(this, function(text) { - if (!text) { - //console.log("Loader._exists: removing " + dir + " from the include path because it doesn't exist."); - this.includePath = this.includePath.slice(-1); - } - })); - } + var fullpath = Path.normalize(Path.join(dir, file)); + if (this.protocol !== "http://") { + var text = this._loadFile(fullpath, true); + if (text && this.includePath.indexOf(text) === -1) { + this.includePath.push(dir); + } + } else { + // put the dir on the list now assuming it exists, and check for its availability + // later so we can avoid the 404 errors eventually + if (this.includePath.indexOf(dir) === -1) { + this.includePath.push(dir); + this._loadFile(fullpath, false, ilib.bind(this, function(text) { + if (!text) { + //console.log("Loader._exists: removing " + dir + " from the include path because it doesn't exist."); + this.includePath = this.includePath.slice(-1); + } + })); + } + } }; Loader.prototype._loadFileAlongIncludePath = function(includePath, pathname) { - for (var i = 0; i < includePath.length; i++) { - var manifest = this.manifest[includePath[i]]; - if (!manifest || Loader.indexOf(manifest, pathname) > -1) { - var filepath = Path.join(includePath[i], pathname); - //console.log("Loader._loadFileAlongIncludePath: attempting sync load " + filepath); - var text = this._loadFile(filepath, true); - if (text) { - //console.log("Loader._loadFileAlongIncludePath: succeeded"); - return text; - } - //else { - //console.log("Loader._loadFileAlongIncludePath: failed"); - //} - } - //else { - //console.log("Loader._loadFileAlongIncludePath: " + pathname + " not in manifest for " + this.includePath[i]); - //} - } - - //console.log("Loader._loadFileAlongIncludePath: file not found anywhere along the path."); - return undefined; + for (var i = 0; i < includePath.length; i++) { + var manifest = this.manifest[includePath[i]]; + if (!manifest || Loader.indexOf(manifest, pathname) > -1) { + var filepath = Path.join(includePath[i], pathname); + //console.log("Loader._loadFileAlongIncludePath: attempting sync load " + filepath); + var text = this._loadFile(filepath, true); + if (text) { + //console.log("Loader._loadFileAlongIncludePath: succeeded"); + return text; + } + //else { + //console.log("Loader._loadFileAlongIncludePath: failed"); + //} + } + //else { + //console.log("Loader._loadFileAlongIncludePath: " + pathname + " not in manifest for " + this.includePath[i]); + //} + } + + //console.log("Loader._loadFileAlongIncludePath: file not found anywhere along the path."); + return undefined; }; Loader.prototype.loadFiles = function(paths, sync, params, callback) { - var includePath = params && params.base ? [params.base].concat(this.includePath) : this.includePath; + var includePath = params && params.base ? [params.base].concat(this.includePath) : this.includePath; + + //console.log("Loader loadFiles called"); + // make sure we know what we can load + if (!paths) { + // nothing to load + //console.log("nothing to load"); + return; + } + + //console.log("generic loader: attempting to load these files: " + JSON.stringify(paths) + "\n"); + if (sync) { + var ret = []; - //console.log("Loader loadFiles called"); - // make sure we know what we can load - if (!paths) { - // nothing to load - //console.log("nothing to load"); - return; - } + // synchronous + this._loadManifests(true); - //console.log("generic loader: attempting to load these files: " + JSON.stringify(paths) + "\n"); - if (sync) { - var ret = []; - - // synchronous - this._loadManifests(true); - - for (var i = 0; i < paths.length; i++) { - var text = this._loadFileAlongIncludePath(includePath, Path.normalize(paths[i])); - ret.push(typeof(text) === "string" ? JSON.parse(text) : text); - }; + for (var i = 0; i < paths.length; i++) { + var text = this._loadFileAlongIncludePath(includePath, Path.normalize(paths[i])); + ret.push(typeof(text) === "string" ? JSON.parse(text) : text); + }; - // only call the callback at the end of the chain of files - if (typeof(callback) === 'function') { - callback(ret); - } + // only call the callback at the end of the chain of files + if (typeof(callback) === 'function') { + callback(ret); + } - return ret; - } + return ret; + } - // asynchronous - this._loadManifests(false, ilib.bind(this, function() { - //console.log("Loader.loadFiles: now loading files asynchronously"); - var results = []; - this._loadFilesAsync(includePath, paths, results, callback); - })); + // asynchronous + this._loadManifests(false, ilib.bind(this, function() { + //console.log("Loader.loadFiles: now loading files asynchronously"); + var results = []; + this._loadFilesAsync(includePath, paths, results, callback); + })); }; Loader.prototype._loadFilesAsyncAlongIncludePath = function (includes, filename, cb) { - var text = undefined; - - if (includes.length > 0) { - var root = includes[0]; - includes = includes.slice(1); - - var manifest = this.manifest[root]; - if (!manifest || Loader.indexOf(manifest, filename) > -1) { - var filepath = Path.join(root, filename); - this._loadFile(filepath, false, ilib.bind(this, function(t) { - //console.log("Loader._loadFilesAsyncAlongIncludePath: loading " + (t ? " success" : " failed")); - if (t) { - cb(t); - } else { - this._loadFilesAsyncAlongIncludePath(includes, filename, cb); - } - })); - } else { - //console.log("Loader._loadFilesAsyncAlongIncludePath: " + filepath + " not in manifest for " + root); - this._loadFilesAsyncAlongIncludePath(includes, filename, cb); - } - } else { - // file not found in any of the include paths - cb(); - } + var text = undefined; + + if (includes.length > 0) { + var root = includes[0]; + includes = includes.slice(1); + + var manifest = this.manifest[root]; + if (!manifest || Loader.indexOf(manifest, filename) > -1) { + var filepath = Path.join(root, filename); + this._loadFile(filepath, false, ilib.bind(this, function(t) { + //console.log("Loader._loadFilesAsyncAlongIncludePath: loading " + (t ? " success" : " failed")); + if (t) { + cb(t); + } else { + this._loadFilesAsyncAlongIncludePath(includes, filename, cb); + } + })); + } else { + //console.log("Loader._loadFilesAsyncAlongIncludePath: " + filepath + " not in manifest for " + root); + this._loadFilesAsyncAlongIncludePath(includes, filename, cb); + } + } else { + // file not found in any of the include paths + cb(); + } }; Loader.prototype._loadFilesAsync = function (includePath, paths, results, callback) { - if (paths.length > 0) { - var filename = paths[0]; - paths = paths.slice(1); - - //console.log("Loader._loadFilesAsync: attempting to load " + filename + " along the include path."); - this._loadFilesAsyncAlongIncludePath(includePath, filename, ilib.bind(this, function (json) { - results.push(typeof(json) === "string" ? JSON.parse(json) : json); - this._loadFilesAsync(includePath, paths, results, callback); - })); - } else { - // only call the callback at the end of the chain of files - if (typeof(callback) === 'function') { - callback(results); - } - } + if (paths.length > 0) { + var filename = paths[0]; + paths = paths.slice(1); + + //console.log("Loader._loadFilesAsync: attempting to load " + filename + " along the include path."); + this._loadFilesAsyncAlongIncludePath(includePath, filename, ilib.bind(this, function (json) { + results.push(typeof(json) === "string" ? JSON.parse(json) : json); + this._loadFilesAsync(includePath, paths, results, callback); + })); + } else { + // only call the callback at the end of the chain of files + if (typeof(callback) === 'function') { + callback(results); + } + } }; Loader.prototype._loadManifestFile = function(i, sync, cb) { @@ -191,77 +193,77 @@ Loader.prototype._loadManifestFile = function(i, sync, cb) { }; Loader.prototype._loadManifests = function(sync, cb) { - //console.log("Loader._loadManifests: called " + (sync ? "synchronously" : "asychronously.")); - if (!this.manifest) { - //console.log("Loader._loadManifests: attempting to find manifests"); - this.manifest = {}; - if (typeof(sync) !== 'boolean') { - sync = true; - } - - this._loadManifestFile(0, sync, cb); - } else { - //console.log("Loader._loadManifests: already loaded"); - if (typeof(cb) === 'function') { - //console.log("Loader._loadManifests: now calling callback function"); - cb(); - } - } + //console.log("Loader._loadManifests: called " + (sync ? "synchronously" : "asychronously.")); + if (!this.manifest) { + //console.log("Loader._loadManifests: attempting to find manifests"); + this.manifest = {}; + if (typeof(sync) !== 'boolean') { + sync = true; + } + + this._loadManifestFile(0, sync, cb); + } else { + //console.log("Loader._loadManifests: already loaded"); + if (typeof(cb) === 'function') { + //console.log("Loader._loadManifests: now calling callback function"); + cb(); + } + } }; Loader.prototype.listAvailableFiles = function(sync, cb) { - //console.log("generic loader: list available files called"); - this._loadManifests(sync, ilib.bind(this, function () { - if (typeof(cb) === 'function') { - //console.log("generic loader: now calling caller's callback function"); - cb(this.manifest); - } - })); - return this.manifest; + //console.log("generic loader: list available files called"); + this._loadManifests(sync, ilib.bind(this, function () { + if (typeof(cb) === 'function') { + //console.log("generic loader: now calling caller's callback function"); + cb(this.manifest); + } + })); + return this.manifest; }; Loader.indexOf = function(array, obj) { - if (!array || !obj) { - return -1; - } - if (typeof(array.indexOf) === 'function') { - return array.indexOf(obj); - } else { - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { - return i; - } - } - return -1; - } + if (!array || !obj) { + return -1; + } + if (typeof(array.indexOf) === 'function') { + return array.indexOf(obj); + } else { + for (var i = 0; i < array.length; i++) { + if (array[i] === obj) { + return i; + } + } + return -1; + } }; Loader.prototype.checkAvailability = function(file) { - for (var dir in this.manifest) { - if (Loader.indexOf(this.manifest[dir], file) !== -1) { - return true; - } - } - - return false; + for (var dir in this.manifest) { + if (Loader.indexOf(this.manifest[dir], file) !== -1) { + return true; + } + } + + return false; }; Loader.prototype.isAvailable = function(file, sync, cb) { - //console.log("Loader.isAvailable: called"); - if (typeof(sync) !== 'boolean') { - sync = true; - } - if (sync) { - this._loadManifests(sync); - return this.checkAvailability(file); - } - - this._loadManifests(false, ilib.bind(this, function () { - // console.log("generic loader: isAvailable " + path + "? "); - if (typeof(cb) === 'function') { - cb(this.checkAvailability(file)); - } - })); + //console.log("Loader.isAvailable: called"); + if (typeof(sync) !== 'boolean') { + sync = true; + } + if (sync) { + this._loadManifests(sync); + return this.checkAvailability(file); + } + + this._loadManifests(false, ilib.bind(this, function () { + // console.log("generic loader: isAvailable " + path + "? "); + if (typeof(cb) === 'function') { + cb(this.checkAvailability(file)); + } + })); }; module.exports = Loader; \ No newline at end of file diff --git a/js/lib/Locale.js b/js/lib/Locale.js index a52726a245..88e4a08a3b 100644 --- a/js/lib/Locale.js +++ b/js/lib/Locale.js @@ -1,6 +1,6 @@ /* * Locale.js - Locale specifier definition - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,45 +22,45 @@ var JSUtils = require("./JSUtils.js"); /** * @class - * Create a new locale instance. Locales are specified either with a specifier string - * that follows the BCP-47 convention (roughly: "language-region-script-variant") or + * Create a new locale instance. Locales are specified either with a specifier string + * that follows the BCP-47 convention (roughly: "language-region-script-variant") or * with 4 parameters that specify the language, region, variant, and script individually. - * + * * The language is given as an ISO 639-1 two-letter, lower-case language code. You - * can find a full list of these codes at + * can find a full list of these codes at * http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
- * + * * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can - * find a full list of these codes at + * find a full list of these codes at * http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2.
- * + * * The variant is any string that does not contain a dash which further differentiates * locales from each other.
- * + * * The script is given as the ISO 15924 four-letter script code. In some locales, * text may be validly written in more than one script. For example, Serbian is often * written in both Latin and Cyrillic, though not usually mixed together. You can find a - * full list of these codes at + * full list of these codes at * http://en.wikipedia.org/wiki/ISO_15924#List_of_codes.
- * - * As an example in ilib, the script can be used in the date formatter. Dates formatted + * + * As an example in ilib, the script can be used in the date formatter. Dates formatted * in Serbian could have day-of-week names or month names written in the Latin * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same - * as sr-SR so the script code "Latn" can be left off of the locale spec.
- * - * Each part is optional, and an empty string in the specifier before or after a + * as sr-SR so the script code "Latn" can be left off of the locale spec.
+ * + * Each part is optional, and an empty string in the specifier before or after a * dash or as a parameter to the constructor denotes an unspecified value. In this * case, many of the ilib functions will treat the locale as generic. For example * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale * of "English" with an unspecified region and variant, which typically matches * any region or variant.
- * + * * Without any arguments to the constructor, this function returns the locale of * the host Javascript engine.
- * - * + * + * * @constructor - * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full + * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full * locale spec in BCP-47 format, or another Locale instance to copy from * @param {string=} region the ISO 3166 2-letter code for the region * @param {string=} variant the name of the variant of this locale, if any @@ -68,514 +68,514 @@ var JSUtils = require("./JSUtils.js"); */ var Locale = function(language, region, variant, script) { if (typeof(region) === 'undefined' && typeof(variant) === 'undefined' && typeof(script) === 'undefined') { - var spec = language || ilib.getLocale(); - if (typeof(spec) === 'string') { - var parts = spec.split('-'); - for ( var i = 0; i < parts.length; i++ ) { - if (Locale._isLanguageCode(parts[i])) { - /** - * @private - * @type {string|undefined} - */ - this.language = parts[i]; - } else if (Locale._isRegionCode(parts[i])) { - /** - * @private - * @type {string|undefined} - */ - this.region = parts[i]; - } else if (Locale._isScriptCode(parts[i])) { - /** - * @private - * @type {string|undefined} - */ - this.script = parts[i]; - } else { - /** - * @private - * @type {string|undefined} - */ - this.variant = parts[i]; - } - } - this.language = this.language || undefined; - this.region = this.region || undefined; - this.script = this.script || undefined; - this.variant = this.variant || undefined; - } else if (typeof(spec) === 'object') { - this.language = spec.language || undefined; - this.region = spec.region || undefined; - this.script = spec.script || undefined; - this.variant = spec.variant || undefined; - } - } else { - if (language && typeof(language) === "string") { - language = language.trim(); - this.language = language.length > 0 ? language.toLowerCase() : undefined; - } else { - this.language = undefined; - } - if (region && typeof(region) === "string") { - region = region.trim(); - this.region = region.length > 0 ? region.toUpperCase() : undefined; - } else { - this.region = undefined; - } - if (variant && typeof(variant) === "string") { - variant = variant.trim(); - this.variant = variant.length > 0 ? variant : undefined; - } else { - this.variant = undefined; - } - if (script && typeof(script) === "string") { - script = script.trim(); - this.script = script.length > 0 ? script : undefined; - } else { - this.script = undefined; - } - } - this._genSpec(); + var spec = language || ilib.getLocale(); + if (typeof(spec) === 'string') { + var parts = spec.split('-'); + for ( var i = 0; i < parts.length; i++ ) { + if (Locale._isLanguageCode(parts[i])) { + /** + * @private + * @type {string|undefined} + */ + this.language = parts[i]; + } else if (Locale._isRegionCode(parts[i])) { + /** + * @private + * @type {string|undefined} + */ + this.region = parts[i]; + } else if (Locale._isScriptCode(parts[i])) { + /** + * @private + * @type {string|undefined} + */ + this.script = parts[i]; + } else { + /** + * @private + * @type {string|undefined} + */ + this.variant = parts[i]; + } + } + this.language = this.language || undefined; + this.region = this.region || undefined; + this.script = this.script || undefined; + this.variant = this.variant || undefined; + } else if (typeof(spec) === 'object') { + this.language = spec.language || undefined; + this.region = spec.region || undefined; + this.script = spec.script || undefined; + this.variant = spec.variant || undefined; + } + } else { + if (language && typeof(language) === "string") { + language = language.trim(); + this.language = language.length > 0 ? language.toLowerCase() : undefined; + } else { + this.language = undefined; + } + if (region && typeof(region) === "string") { + region = region.trim(); + this.region = region.length > 0 ? region.toUpperCase() : undefined; + } else { + this.region = undefined; + } + if (variant && typeof(variant) === "string") { + variant = variant.trim(); + this.variant = variant.length > 0 ? variant : undefined; + } else { + this.variant = undefined; + } + if (script && typeof(script) === "string") { + script = script.trim(); + this.script = script.length > 0 ? script : undefined; + } else { + this.script = undefined; + } + } + this._genSpec(); }; // from http://en.wikipedia.org/wiki/ISO_3166-1 Locale.a2toa3regmap = { - "AF": "AFG", - "AX": "ALA", - "AL": "ALB", - "DZ": "DZA", - "AS": "ASM", - "AD": "AND", - "AO": "AGO", - "AI": "AIA", - "AQ": "ATA", - "AG": "ATG", - "AR": "ARG", - "AM": "ARM", - "AW": "ABW", - "AU": "AUS", - "AT": "AUT", - "AZ": "AZE", - "BS": "BHS", - "BH": "BHR", - "BD": "BGD", - "BB": "BRB", - "BY": "BLR", - "BE": "BEL", - "BZ": "BLZ", - "BJ": "BEN", - "BM": "BMU", - "BT": "BTN", - "BO": "BOL", - "BQ": "BES", - "BA": "BIH", - "BW": "BWA", - "BV": "BVT", - "BR": "BRA", - "IO": "IOT", - "BN": "BRN", - "BG": "BGR", - "BF": "BFA", - "BI": "BDI", - "KH": "KHM", - "CM": "CMR", - "CA": "CAN", - "CV": "CPV", - "KY": "CYM", - "CF": "CAF", - "TD": "TCD", - "CL": "CHL", - "CN": "CHN", - "CX": "CXR", - "CC": "CCK", - "CO": "COL", - "KM": "COM", - "CG": "COG", - "CD": "COD", - "CK": "COK", - "CR": "CRI", - "CI": "CIV", - "HR": "HRV", - "CU": "CUB", - "CW": "CUW", - "CY": "CYP", - "CZ": "CZE", - "DK": "DNK", - "DJ": "DJI", - "DM": "DMA", - "DO": "DOM", - "EC": "ECU", - "EG": "EGY", - "SV": "SLV", - "GQ": "GNQ", - "ER": "ERI", - "EE": "EST", - "ET": "ETH", - "FK": "FLK", - "FO": "FRO", - "FJ": "FJI", - "FI": "FIN", - "FR": "FRA", - "GF": "GUF", - "PF": "PYF", - "TF": "ATF", - "GA": "GAB", - "GM": "GMB", - "GE": "GEO", - "DE": "DEU", - "GH": "GHA", - "GI": "GIB", - "GR": "GRC", - "GL": "GRL", - "GD": "GRD", - "GP": "GLP", - "GU": "GUM", - "GT": "GTM", - "GG": "GGY", - "GN": "GIN", - "GW": "GNB", - "GY": "GUY", - "HT": "HTI", - "HM": "HMD", - "VA": "VAT", - "HN": "HND", - "HK": "HKG", - "HU": "HUN", - "IS": "ISL", - "IN": "IND", - "ID": "IDN", - "IR": "IRN", - "IQ": "IRQ", - "IE": "IRL", - "IM": "IMN", - "IL": "ISR", - "IT": "ITA", - "JM": "JAM", - "JP": "JPN", - "JE": "JEY", - "JO": "JOR", - "KZ": "KAZ", - "KE": "KEN", - "KI": "KIR", - "KP": "PRK", - "KR": "KOR", - "KW": "KWT", - "KG": "KGZ", - "LA": "LAO", - "LV": "LVA", - "LB": "LBN", - "LS": "LSO", - "LR": "LBR", - "LY": "LBY", - "LI": "LIE", - "LT": "LTU", - "LU": "LUX", - "MO": "MAC", - "MK": "MKD", - "MG": "MDG", - "MW": "MWI", - "MY": "MYS", - "MV": "MDV", - "ML": "MLI", - "MT": "MLT", - "MH": "MHL", - "MQ": "MTQ", - "MR": "MRT", - "MU": "MUS", - "YT": "MYT", - "MX": "MEX", - "FM": "FSM", - "MD": "MDA", - "MC": "MCO", - "MN": "MNG", - "ME": "MNE", - "MS": "MSR", - "MA": "MAR", - "MZ": "MOZ", - "MM": "MMR", - "NA": "NAM", - "NR": "NRU", - "NP": "NPL", - "NL": "NLD", - "NC": "NCL", - "NZ": "NZL", - "NI": "NIC", - "NE": "NER", - "NG": "NGA", - "NU": "NIU", - "NF": "NFK", - "MP": "MNP", - "NO": "NOR", - "OM": "OMN", - "PK": "PAK", - "PW": "PLW", - "PS": "PSE", - "PA": "PAN", - "PG": "PNG", - "PY": "PRY", - "PE": "PER", - "PH": "PHL", - "PN": "PCN", - "PL": "POL", - "PT": "PRT", - "PR": "PRI", - "QA": "QAT", - "RE": "REU", - "RO": "ROU", - "RU": "RUS", - "RW": "RWA", - "BL": "BLM", - "SH": "SHN", - "KN": "KNA", - "LC": "LCA", - "MF": "MAF", - "PM": "SPM", - "VC": "VCT", - "WS": "WSM", - "SM": "SMR", - "ST": "STP", - "SA": "SAU", - "SN": "SEN", - "RS": "SRB", - "SC": "SYC", - "SL": "SLE", - "SG": "SGP", - "SX": "SXM", - "SK": "SVK", - "SI": "SVN", - "SB": "SLB", - "SO": "SOM", - "ZA": "ZAF", - "GS": "SGS", - "SS": "SSD", - "ES": "ESP", - "LK": "LKA", - "SD": "SDN", - "SR": "SUR", - "SJ": "SJM", - "SZ": "SWZ", - "SE": "SWE", - "CH": "CHE", - "SY": "SYR", - "TW": "TWN", - "TJ": "TJK", - "TZ": "TZA", - "TH": "THA", - "TL": "TLS", - "TG": "TGO", - "TK": "TKL", - "TO": "TON", - "TT": "TTO", - "TN": "TUN", - "TR": "TUR", - "TM": "TKM", - "TC": "TCA", - "TV": "TUV", - "UG": "UGA", - "UA": "UKR", - "AE": "ARE", - "GB": "GBR", - "US": "USA", - "UM": "UMI", - "UY": "URY", - "UZ": "UZB", - "VU": "VUT", - "VE": "VEN", - "VN": "VNM", - "VG": "VGB", - "VI": "VIR", - "WF": "WLF", - "EH": "ESH", - "YE": "YEM", - "ZM": "ZMB", - "ZW": "ZWE" + "AF": "AFG", + "AX": "ALA", + "AL": "ALB", + "DZ": "DZA", + "AS": "ASM", + "AD": "AND", + "AO": "AGO", + "AI": "AIA", + "AQ": "ATA", + "AG": "ATG", + "AR": "ARG", + "AM": "ARM", + "AW": "ABW", + "AU": "AUS", + "AT": "AUT", + "AZ": "AZE", + "BS": "BHS", + "BH": "BHR", + "BD": "BGD", + "BB": "BRB", + "BY": "BLR", + "BE": "BEL", + "BZ": "BLZ", + "BJ": "BEN", + "BM": "BMU", + "BT": "BTN", + "BO": "BOL", + "BQ": "BES", + "BA": "BIH", + "BW": "BWA", + "BV": "BVT", + "BR": "BRA", + "IO": "IOT", + "BN": "BRN", + "BG": "BGR", + "BF": "BFA", + "BI": "BDI", + "KH": "KHM", + "CM": "CMR", + "CA": "CAN", + "CV": "CPV", + "KY": "CYM", + "CF": "CAF", + "TD": "TCD", + "CL": "CHL", + "CN": "CHN", + "CX": "CXR", + "CC": "CCK", + "CO": "COL", + "KM": "COM", + "CG": "COG", + "CD": "COD", + "CK": "COK", + "CR": "CRI", + "CI": "CIV", + "HR": "HRV", + "CU": "CUB", + "CW": "CUW", + "CY": "CYP", + "CZ": "CZE", + "DK": "DNK", + "DJ": "DJI", + "DM": "DMA", + "DO": "DOM", + "EC": "ECU", + "EG": "EGY", + "SV": "SLV", + "GQ": "GNQ", + "ER": "ERI", + "EE": "EST", + "ET": "ETH", + "FK": "FLK", + "FO": "FRO", + "FJ": "FJI", + "FI": "FIN", + "FR": "FRA", + "GF": "GUF", + "PF": "PYF", + "TF": "ATF", + "GA": "GAB", + "GM": "GMB", + "GE": "GEO", + "DE": "DEU", + "GH": "GHA", + "GI": "GIB", + "GR": "GRC", + "GL": "GRL", + "GD": "GRD", + "GP": "GLP", + "GU": "GUM", + "GT": "GTM", + "GG": "GGY", + "GN": "GIN", + "GW": "GNB", + "GY": "GUY", + "HT": "HTI", + "HM": "HMD", + "VA": "VAT", + "HN": "HND", + "HK": "HKG", + "HU": "HUN", + "IS": "ISL", + "IN": "IND", + "ID": "IDN", + "IR": "IRN", + "IQ": "IRQ", + "IE": "IRL", + "IM": "IMN", + "IL": "ISR", + "IT": "ITA", + "JM": "JAM", + "JP": "JPN", + "JE": "JEY", + "JO": "JOR", + "KZ": "KAZ", + "KE": "KEN", + "KI": "KIR", + "KP": "PRK", + "KR": "KOR", + "KW": "KWT", + "KG": "KGZ", + "LA": "LAO", + "LV": "LVA", + "LB": "LBN", + "LS": "LSO", + "LR": "LBR", + "LY": "LBY", + "LI": "LIE", + "LT": "LTU", + "LU": "LUX", + "MO": "MAC", + "MK": "MKD", + "MG": "MDG", + "MW": "MWI", + "MY": "MYS", + "MV": "MDV", + "ML": "MLI", + "MT": "MLT", + "MH": "MHL", + "MQ": "MTQ", + "MR": "MRT", + "MU": "MUS", + "YT": "MYT", + "MX": "MEX", + "FM": "FSM", + "MD": "MDA", + "MC": "MCO", + "MN": "MNG", + "ME": "MNE", + "MS": "MSR", + "MA": "MAR", + "MZ": "MOZ", + "MM": "MMR", + "NA": "NAM", + "NR": "NRU", + "NP": "NPL", + "NL": "NLD", + "NC": "NCL", + "NZ": "NZL", + "NI": "NIC", + "NE": "NER", + "NG": "NGA", + "NU": "NIU", + "NF": "NFK", + "MP": "MNP", + "NO": "NOR", + "OM": "OMN", + "PK": "PAK", + "PW": "PLW", + "PS": "PSE", + "PA": "PAN", + "PG": "PNG", + "PY": "PRY", + "PE": "PER", + "PH": "PHL", + "PN": "PCN", + "PL": "POL", + "PT": "PRT", + "PR": "PRI", + "QA": "QAT", + "RE": "REU", + "RO": "ROU", + "RU": "RUS", + "RW": "RWA", + "BL": "BLM", + "SH": "SHN", + "KN": "KNA", + "LC": "LCA", + "MF": "MAF", + "PM": "SPM", + "VC": "VCT", + "WS": "WSM", + "SM": "SMR", + "ST": "STP", + "SA": "SAU", + "SN": "SEN", + "RS": "SRB", + "SC": "SYC", + "SL": "SLE", + "SG": "SGP", + "SX": "SXM", + "SK": "SVK", + "SI": "SVN", + "SB": "SLB", + "SO": "SOM", + "ZA": "ZAF", + "GS": "SGS", + "SS": "SSD", + "ES": "ESP", + "LK": "LKA", + "SD": "SDN", + "SR": "SUR", + "SJ": "SJM", + "SZ": "SWZ", + "SE": "SWE", + "CH": "CHE", + "SY": "SYR", + "TW": "TWN", + "TJ": "TJK", + "TZ": "TZA", + "TH": "THA", + "TL": "TLS", + "TG": "TGO", + "TK": "TKL", + "TO": "TON", + "TT": "TTO", + "TN": "TUN", + "TR": "TUR", + "TM": "TKM", + "TC": "TCA", + "TV": "TUV", + "UG": "UGA", + "UA": "UKR", + "AE": "ARE", + "GB": "GBR", + "US": "USA", + "UM": "UMI", + "UY": "URY", + "UZ": "UZB", + "VU": "VUT", + "VE": "VEN", + "VN": "VNM", + "VG": "VGB", + "VI": "VIR", + "WF": "WLF", + "EH": "ESH", + "YE": "YEM", + "ZM": "ZMB", + "ZW": "ZWE" }; Locale.a1toa3langmap = { - "ab": "abk", - "aa": "aar", - "af": "afr", - "ak": "aka", - "sq": "sqi", - "am": "amh", - "ar": "ara", - "an": "arg", - "hy": "hye", - "as": "asm", - "av": "ava", - "ae": "ave", - "ay": "aym", - "az": "aze", - "bm": "bam", - "ba": "bak", - "eu": "eus", - "be": "bel", - "bn": "ben", - "bh": "bih", - "bi": "bis", - "bs": "bos", - "br": "bre", - "bg": "bul", - "my": "mya", - "ca": "cat", - "ch": "cha", - "ce": "che", - "ny": "nya", - "zh": "zho", - "cv": "chv", - "kw": "cor", - "co": "cos", - "cr": "cre", - "hr": "hrv", - "cs": "ces", - "da": "dan", - "dv": "div", - "nl": "nld", - "dz": "dzo", - "en": "eng", - "eo": "epo", - "et": "est", - "ee": "ewe", - "fo": "fao", - "fj": "fij", - "fi": "fin", - "fr": "fra", - "ff": "ful", - "gl": "glg", - "ka": "kat", - "de": "deu", - "el": "ell", - "gn": "grn", - "gu": "guj", - "ht": "hat", - "ha": "hau", - "he": "heb", - "hz": "her", - "hi": "hin", - "ho": "hmo", - "hu": "hun", - "ia": "ina", - "id": "ind", - "ie": "ile", - "ga": "gle", - "ig": "ibo", - "ik": "ipk", - "io": "ido", - "is": "isl", - "it": "ita", - "iu": "iku", - "ja": "jpn", - "jv": "jav", - "kl": "kal", - "kn": "kan", - "kr": "kau", - "ks": "kas", - "kk": "kaz", - "km": "khm", - "ki": "kik", - "rw": "kin", - "ky": "kir", - "kv": "kom", - "kg": "kon", - "ko": "kor", - "ku": "kur", - "kj": "kua", - "la": "lat", - "lb": "ltz", - "lg": "lug", - "li": "lim", - "ln": "lin", - "lo": "lao", - "lt": "lit", - "lu": "lub", - "lv": "lav", - "gv": "glv", - "mk": "mkd", - "mg": "mlg", - "ms": "msa", - "ml": "mal", - "mt": "mlt", - "mi": "mri", - "mr": "mar", - "mh": "mah", - "mn": "mon", - "na": "nau", - "nv": "nav", - "nb": "nob", - "nd": "nde", - "ne": "nep", - "ng": "ndo", - "nn": "nno", - "no": "nor", - "ii": "iii", - "nr": "nbl", - "oc": "oci", - "oj": "oji", - "cu": "chu", - "om": "orm", - "or": "ori", - "os": "oss", - "pa": "pan", - "pi": "pli", - "fa": "fas", - "pl": "pol", - "ps": "pus", - "pt": "por", - "qu": "que", - "rm": "roh", - "rn": "run", - "ro": "ron", - "ru": "rus", - "sa": "san", - "sc": "srd", - "sd": "snd", - "se": "sme", - "sm": "smo", - "sg": "sag", - "sr": "srp", - "gd": "gla", - "sn": "sna", - "si": "sin", - "sk": "slk", - "sl": "slv", - "so": "som", - "st": "sot", - "es": "spa", - "su": "sun", - "sw": "swa", - "ss": "ssw", - "sv": "swe", - "ta": "tam", - "te": "tel", - "tg": "tgk", - "th": "tha", - "ti": "tir", - "bo": "bod", - "tk": "tuk", - "tl": "tgl", - "tn": "tsn", - "to": "ton", - "tr": "tur", - "ts": "tso", - "tt": "tat", - "tw": "twi", - "ty": "tah", - "ug": "uig", - "uk": "ukr", - "ur": "urd", - "uz": "uzb", - "ve": "ven", - "vi": "vie", - "vo": "vol", - "wa": "wln", - "cy": "cym", - "wo": "wol", - "fy": "fry", - "xh": "xho", - "yi": "yid", - "yo": "yor", - "za": "zha", - "zu": "zul" + "ab": "abk", + "aa": "aar", + "af": "afr", + "ak": "aka", + "sq": "sqi", + "am": "amh", + "ar": "ara", + "an": "arg", + "hy": "hye", + "as": "asm", + "av": "ava", + "ae": "ave", + "ay": "aym", + "az": "aze", + "bm": "bam", + "ba": "bak", + "eu": "eus", + "be": "bel", + "bn": "ben", + "bh": "bih", + "bi": "bis", + "bs": "bos", + "br": "bre", + "bg": "bul", + "my": "mya", + "ca": "cat", + "ch": "cha", + "ce": "che", + "ny": "nya", + "zh": "zho", + "cv": "chv", + "kw": "cor", + "co": "cos", + "cr": "cre", + "hr": "hrv", + "cs": "ces", + "da": "dan", + "dv": "div", + "nl": "nld", + "dz": "dzo", + "en": "eng", + "eo": "epo", + "et": "est", + "ee": "ewe", + "fo": "fao", + "fj": "fij", + "fi": "fin", + "fr": "fra", + "ff": "ful", + "gl": "glg", + "ka": "kat", + "de": "deu", + "el": "ell", + "gn": "grn", + "gu": "guj", + "ht": "hat", + "ha": "hau", + "he": "heb", + "hz": "her", + "hi": "hin", + "ho": "hmo", + "hu": "hun", + "ia": "ina", + "id": "ind", + "ie": "ile", + "ga": "gle", + "ig": "ibo", + "ik": "ipk", + "io": "ido", + "is": "isl", + "it": "ita", + "iu": "iku", + "ja": "jpn", + "jv": "jav", + "kl": "kal", + "kn": "kan", + "kr": "kau", + "ks": "kas", + "kk": "kaz", + "km": "khm", + "ki": "kik", + "rw": "kin", + "ky": "kir", + "kv": "kom", + "kg": "kon", + "ko": "kor", + "ku": "kur", + "kj": "kua", + "la": "lat", + "lb": "ltz", + "lg": "lug", + "li": "lim", + "ln": "lin", + "lo": "lao", + "lt": "lit", + "lu": "lub", + "lv": "lav", + "gv": "glv", + "mk": "mkd", + "mg": "mlg", + "ms": "msa", + "ml": "mal", + "mt": "mlt", + "mi": "mri", + "mr": "mar", + "mh": "mah", + "mn": "mon", + "na": "nau", + "nv": "nav", + "nb": "nob", + "nd": "nde", + "ne": "nep", + "ng": "ndo", + "nn": "nno", + "no": "nor", + "ii": "iii", + "nr": "nbl", + "oc": "oci", + "oj": "oji", + "cu": "chu", + "om": "orm", + "or": "ori", + "os": "oss", + "pa": "pan", + "pi": "pli", + "fa": "fas", + "pl": "pol", + "ps": "pus", + "pt": "por", + "qu": "que", + "rm": "roh", + "rn": "run", + "ro": "ron", + "ru": "rus", + "sa": "san", + "sc": "srd", + "sd": "snd", + "se": "sme", + "sm": "smo", + "sg": "sag", + "sr": "srp", + "gd": "gla", + "sn": "sna", + "si": "sin", + "sk": "slk", + "sl": "slv", + "so": "som", + "st": "sot", + "es": "spa", + "su": "sun", + "sw": "swa", + "ss": "ssw", + "sv": "swe", + "ta": "tam", + "te": "tel", + "tg": "tgk", + "th": "tha", + "ti": "tir", + "bo": "bod", + "tk": "tuk", + "tl": "tgl", + "tn": "tsn", + "to": "ton", + "tr": "tur", + "ts": "tso", + "tt": "tat", + "tw": "twi", + "ty": "tah", + "ug": "uig", + "uk": "ukr", + "ur": "urd", + "uz": "uzb", + "ve": "ven", + "vi": "vie", + "vo": "vol", + "wa": "wln", + "cy": "cym", + "wo": "wol", + "fy": "fry", + "xh": "xho", + "yi": "yid", + "yo": "yor", + "za": "zha", + "zu": "zul" }; /** @@ -585,9 +585,9 @@ Locale.a1toa3langmap = { * @return {boolean} true if the char is not a lower case ASCII char */ Locale._notLower = function(str) { - // do this with ASCII only so we don't have to depend on the CType functions - var ch = str.charCodeAt(0); - return ch < 97 || ch > 122; + // do this with ASCII only so we don't have to depend on the CType functions + var ch = str.charCodeAt(0); + return ch < 97 || ch > 122; }; /** @@ -597,9 +597,9 @@ Locale._notLower = function(str) { * @return {boolean} true if the char is a not an upper case ASCII char */ Locale._notUpper = function(str) { - // do this with ASCII only so we don't have to depend on the CType functions - var ch = str.charCodeAt(0); - return ch < 65 || ch > 90; + // do this with ASCII only so we don't have to depend on the CType functions + var ch = str.charCodeAt(0); + return ch < 65 || ch > 90; }; /** @@ -609,85 +609,85 @@ Locale._notUpper = function(str) { * @return {boolean} true if the char is a not an upper case ASCII char */ Locale._notDigit = function(str) { - // do this with ASCII only so we don't have to depend on the CType functions - var ch = str.charCodeAt(0); - return ch < 48 || ch > 57; + // do this with ASCII only so we don't have to depend on the CType functions + var ch = str.charCodeAt(0); + return ch < 48 || ch > 57; }; /** - * Tell whether or not the given string has the correct syntax to be + * Tell whether or not the given string has the correct syntax to be * an ISO 639 language code. - * + * * @private * @param {string} str the string to parse * @return {boolean} true if the string could syntactically be a language code. */ Locale._isLanguageCode = function(str) { - if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { - return false; - } - - for (var i = 0; i < str.length; i++) { - if (Locale._notLower(str.charAt(i))) { - return false; - } - } - - return true; + if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { + return false; + } + + for (var i = 0; i < str.length; i++) { + if (Locale._notLower(str.charAt(i))) { + return false; + } + } + + return true; }; /** - * Tell whether or not the given string has the correct syntax to be + * Tell whether or not the given string has the correct syntax to be * an ISO 3166 2-letter region code or M.49 3-digit region code. - * + * * @private * @param {string} str the string to parse * @return {boolean} true if the string could syntactically be a language code. */ Locale._isRegionCode = function (str) { var i; - - if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { - return false; - } - - if (str.length === 2) { - for (i = 0; i < str.length; i++) { - if (Locale._notUpper(str.charAt(i))) { - return false; - } - } - } else { - for (i = 0; i < str.length; i++) { - if (Locale._notDigit(str.charAt(i))) { - return false; - } - } - } - - return true; + + if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { + return false; + } + + if (str.length === 2) { + for (i = 0; i < str.length; i++) { + if (Locale._notUpper(str.charAt(i))) { + return false; + } + } + } else { + for (i = 0; i < str.length; i++) { + if (Locale._notDigit(str.charAt(i))) { + return false; + } + } + } + + return true; }; /** - * Tell whether or not the given string has the correct syntax to be + * Tell whether or not the given string has the correct syntax to be * an ISO 639 language code. - * + * * @private * @param {string} str the string to parse * @return {boolean} true if the string could syntactically be a language code. */ Locale._isScriptCode = function(str) { - if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { - return false; - } - - for (var i = 1; i < 4; i++) { - if (Locale._notLower(str.charAt(i))) { - return false; - } - } - - return true; + if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { + return false; + } + + for (var i = 1; i < 4; i++) { + if (Locale._notLower(str.charAt(i))) { + return false; + } + } + + return true; }; /** @@ -700,7 +700,7 @@ Locale._isScriptCode = function(str) { * parameter if the alpha2 value is not found */ Locale.regionAlpha2ToAlpha3 = function(alpha2) { - return Locale.a2toa3regmap[alpha2] || alpha2; + return Locale.a2toa3regmap[alpha2] || alpha2; }; /** @@ -713,139 +713,139 @@ Locale.regionAlpha2ToAlpha3 = function(alpha2) { * parameter if the alpha1 value is not found */ Locale.languageAlpha1ToAlpha3 = function(alpha1) { - return Locale.a1toa3langmap[alpha1] || alpha1; + return Locale.a1toa3langmap[alpha1] || alpha1; }; Locale.prototype = { - /** - * @private - */ - _genSpec: function () { - this.spec = this.language || ""; - - if (this.script) { - if (this.spec.length > 0) { - this.spec += "-"; - } - this.spec += this.script; - } - - if (this.region) { - if (this.spec.length > 0) { - this.spec += "-"; - } - this.spec += this.region; - } - - if (this.variant) { - if (this.spec.length > 0) { - this.spec += "-"; - } - this.spec += this.variant; - } - }, - - /** - * Return the ISO 639 language code for this locale. - * @return {string|undefined} the language code for this locale - */ - getLanguage: function() { - return this.language; - }, - - /** - * Return the language of this locale as an ISO-639-alpha3 language code - * @return {string|undefined} the alpha3 language code of this locale - */ - getLanguageAlpha3: function() { - return Locale.languageAlpha1ToAlpha3(this.language); - }, - - /** - * Return the ISO 3166 region code for this locale. - * @return {string|undefined} the region code of this locale - */ - getRegion: function() { - return this.region; - }, - - /** - * Return the region of this locale as an ISO-3166-alpha3 region code - * @return {string|undefined} the alpha3 region code of this locale - */ - getRegionAlpha3: function() { - return Locale.regionAlpha2ToAlpha3(this.region); - }, - - /** - * Return the ISO 15924 script code for this locale - * @return {string|undefined} the script code of this locale - */ - getScript: function () { - return this.script; - }, - - /** - * Return the variant code for this locale - * @return {string|undefined} the variant code of this locale, if any - */ - getVariant: function() { - return this.variant; - }, - - /** - * Return the whole locale specifier as a string. - * @return {string} the locale specifier - */ - getSpec: function() { - if (!this.spec) this._genSpec(); - return this.spec; - }, - - /** - * Return the language locale specifier. This includes the - * language and the script if it is available. This can be - * used to see whether the written language of two locales - * match each other regardless of the region or variant. - * - * @return {string} the language locale specifier - */ - getLangSpec: function() { - var spec = this.language; - if (spec && this.script) { - spec += "-" + this.script; - } - return spec || ""; - }, - - /** - * Express this locale object as a string. Currently, this simply calls the getSpec - * function to represent the locale as its specifier. - * - * @return {string} the locale specifier - */ - toString: function() { - return this.getSpec(); - }, - - /** - * Return true if the the other locale is exactly equal to the current one. - * @return {boolean} whether or not the other locale is equal to the current one - */ - equals: function(other) { - return this.language === other.language && - this.region === other.region && - this.script === other.script && - this.variant === other.variant; - }, - - /** - * Return true if the current locale is the special pseudo locale. - * @return {boolean} true if the current locale is the special pseudo locale - */ - isPseudo: function () { - return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; - } + /** + * @private + */ + _genSpec: function () { + this.spec = this.language || ""; + + if (this.script) { + if (this.spec.length > 0) { + this.spec += "-"; + } + this.spec += this.script; + } + + if (this.region) { + if (this.spec.length > 0) { + this.spec += "-"; + } + this.spec += this.region; + } + + if (this.variant) { + if (this.spec.length > 0) { + this.spec += "-"; + } + this.spec += this.variant; + } + }, + + /** + * Return the ISO 639 language code for this locale. + * @return {string|undefined} the language code for this locale + */ + getLanguage: function() { + return this.language; + }, + + /** + * Return the language of this locale as an ISO-639-alpha3 language code + * @return {string|undefined} the alpha3 language code of this locale + */ + getLanguageAlpha3: function() { + return Locale.languageAlpha1ToAlpha3(this.language); + }, + + /** + * Return the ISO 3166 region code for this locale. + * @return {string|undefined} the region code of this locale + */ + getRegion: function() { + return this.region; + }, + + /** + * Return the region of this locale as an ISO-3166-alpha3 region code + * @return {string|undefined} the alpha3 region code of this locale + */ + getRegionAlpha3: function() { + return Locale.regionAlpha2ToAlpha3(this.region); + }, + + /** + * Return the ISO 15924 script code for this locale + * @return {string|undefined} the script code of this locale + */ + getScript: function () { + return this.script; + }, + + /** + * Return the variant code for this locale + * @return {string|undefined} the variant code of this locale, if any + */ + getVariant: function() { + return this.variant; + }, + + /** + * Return the whole locale specifier as a string. + * @return {string} the locale specifier + */ + getSpec: function() { + if (!this.spec) this._genSpec(); + return this.spec; + }, + + /** + * Return the language locale specifier. This includes the + * language and the script if it is available. This can be + * used to see whether the written language of two locales + * match each other regardless of the region or variant. + * + * @return {string} the language locale specifier + */ + getLangSpec: function() { + var spec = this.language; + if (spec && this.script) { + spec += "-" + this.script; + } + return spec || ""; + }, + + /** + * Express this locale object as a string. Currently, this simply calls the getSpec + * function to represent the locale as its specifier. + * + * @return {string} the locale specifier + */ + toString: function() { + return this.getSpec(); + }, + + /** + * Return true if the the other locale is exactly equal to the current one. + * @return {boolean} whether or not the other locale is equal to the current one + */ + equals: function(other) { + return this.language === other.language && + this.region === other.region && + this.script === other.script && + this.variant === other.variant; + }, + + /** + * Return true if the current locale is the special pseudo locale. + * @return {boolean} true if the current locale is the special pseudo locale + */ + isPseudo: function () { + return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; + } }; // static functions @@ -857,59 +857,59 @@ Locale.locales = []; /** * Return the list of available locales that this iLib file supports. - * If this copy of ilib is pre-assembled with locale data, then the + * If this copy of ilib is pre-assembled with locale data, then the * list locales may be much smaller * than the list of all available locales in the iLib repository. The * assembly tool will automatically fill in the list for an assembled - * copy of iLib. If this copy is being used with dynamically loaded - * data, then you - * can load any locale that iLib supports. You can form a locale with any + * copy of iLib. If this copy is being used with dynamically loaded + * data, then you + * can load any locale that iLib supports. You can form a locale with any * combination of a language and region tags that exist in the locale * data directory. Language tags are in the root of the locale data dir, - * and region tags can be found underneath the "und" directory. (The - * region tags are separated into a different dir because the region names - * conflict with language names on file systems that are case-insensitive.) + * and region tags can be found underneath the "und" directory. (The + * region tags are separated into a different dir because the region names + * conflict with language names on file systems that are case-insensitive.) * If you have culled the locale data directory to limit the size of * your app, then this function should return only those files that actually exist * according to the ilibmanifest.json file in the root of that locale * data dir. Make sure your ilibmanifest.json file is up-to-date with * respect to the list of files that exist in the locale data dir. - * + * * @param {boolean} sync if false, load the list of available files from disk * asynchronously, otherwise load them synchronously. (Default: true/synchronously) * @param {Function} onLoad a callback function to call if asynchronous * load was requested and the list of files have been loaded. - * @return {Array.
} this is an array of locale specs for which + * @return {Array. } this is an array of locale specs for which * this iLib file has locale data for */ Locale.getAvailableLocales = function (sync, onLoad) { - var locales = []; - if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { - locales = Locale.locales; - if (onLoad && typeof(onLoad) === 'function') { - onLoad(locales); - } - } else { - if (typeof(sync) === 'undefined') { - sync = true; - } - ilib._load.listAvailableFiles(sync, function(manifest) { - if (manifest) { - for (var dir in manifest) { - var filelist = manifest[dir]; - for (var i = 0; i < filelist.length; i++) { - if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { - locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); - } - } - } - } - if (onLoad && typeof(onLoad) === 'function') { - onLoad(locales); - } - }); - } - return locales; + var locales = []; + if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { + locales = Locale.locales; + if (onLoad && typeof(onLoad) === 'function') { + onLoad(locales); + } + } else { + if (typeof(sync) === 'undefined') { + sync = true; + } + ilib._load.listAvailableFiles(sync, function(manifest) { + if (manifest) { + for (var dir in manifest) { + var filelist = manifest[dir]; + for (var i = 0; i < filelist.length; i++) { + if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { + locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); + } + } + } + } + if (onLoad && typeof(onLoad) === 'function') { + onLoad(locales); + } + }); + } + return locales; }; module.exports = Locale; diff --git a/js/lib/LocaleInfo.js b/js/lib/LocaleInfo.js index f616efe1e4..729d7116d7 100644 --- a/js/lib/LocaleInfo.js +++ b/js/lib/LocaleInfo.js @@ -1,6 +1,6 @@ /* * LocaleInfo.js - Encode locale-specific defaults - * + * * Copyright © 2012-2015, 2018, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ // !data localeinfo -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); @@ -29,37 +29,37 @@ var Locale = require("./Locale.js"); * the default settings for a particular locale. These settings may be overridden * by various parts of the code, and should be used as a fall-back setting of last * resort. - * + * * The optional options object holds extra parameters if they are necessary. The * current list of supported options are: - * + * *
- *
- * - * 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 locale data synchronously. - * - * + * + * * @constructor * @see {ilib.setLoaderCallback} for information about registering a loader callback * function @@ -68,78 +68,78 @@ var Locale = require("./Locale.js"); * the current locale */ var LocaleInfo = function(locale, options) { - var sync = true, - loadParams = undefined; - - /** - @private - @type {{ - calendar:string, - clock:string, - currency:string, - delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string}, - firstDayOfWeek:number, - meridiems:string, - numfmt:{ - currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string}, - decimalChar:string, - exponential:string, - groupChar:string, - negativenumFmt:string, - negativepctFmt:string, - pctChar:string, - pctFmt:string, - prigroupSize:number, - roundingMode:string, - script:string, - secgroupSize:number, - useNative:boolean - }, - timezone:string, - units:string, - weekendEnd:number, - weekendStart:number, - paperSizes:{regular:string} - }} - */ - this.info = LocaleInfo.defaultInfo; - - switch (typeof(locale)) { - case "string": - this.locale = new Locale(locale); - break; - default: - case "undefined": - this.locale = new Locale(); - break; - case "object": - this.locale = locale; - break; - } - - if (options) { - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - loadParams = options.loadParams; - } - } - - Utils.loadData({ - object: "LocaleInfo", - locale: this.locale, - name: "localeinfo.json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - this.info = info || LocaleInfo.defaultInfo; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); + var sync = true, + loadParams = undefined; + + /** + @private + @type {{ + calendar:string, + clock:string, + currency:string, + delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string}, + firstDayOfWeek:number, + meridiems:string, + numfmt:{ + currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string}, + decimalChar:string, + exponential:string, + groupChar:string, + negativenumFmt:string, + negativepctFmt:string, + pctChar:string, + pctFmt:string, + prigroupSize:number, + roundingMode:string, + script:string, + secgroupSize:number, + useNative:boolean + }, + timezone:string, + units:string, + weekendEnd:number, + weekendStart:number, + paperSizes:{regular:string} + }} + */ + this.info = LocaleInfo.defaultInfo; + + switch (typeof(locale)) { + case "string": + this.locale = new Locale(locale); + break; + default: + case "undefined": + this.locale = new Locale(); + break; + case "object": + this.locale = locale; + break; + } + + if (options) { + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + loadParams = options.loadParams; + } + } + + Utils.loadData({ + object: "LocaleInfo", + locale: this.locale, + name: "localeinfo.json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + this.info = info || LocaleInfo.defaultInfo; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); }; LocaleInfo.defaultInfo = ilib.data.localeinfo; @@ -190,375 +190,375 @@ LocaleInfo.prototype = { * @returns {string} the name of the locale's language in English */ getLanguageName: function () { - return this.info["language.name"]; + return this.info["language.name"]; }, - + /** * Return the name of the locale's region in English. If the locale * has no region, this returns undefined. - * + * * @returns {string|undefined} the name of the locale's region in English */ getRegionName: function () { - return this.info["region.name"]; - }, - - /** - * Return whether this locale commonly uses the 12- or the 24-hour clock. - * - * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" - * if the locale commonly uses a 24-hour clock. - */ - getClock: function() { - return this.info.clock; - }, - - /** - * Return the locale that this info object was created with. - * @returns {Locale} The locale spec of the locale used to construct this info instance - */ - getLocale: function () { - return this.locale; - }, - - /** - * Return the name of the measuring system that is commonly used in the given locale. - * Valid values are "uscustomary", "imperial", and "metric". - * - * @returns {string} The name of the measuring system commonly used in the locale - */ - getUnits: function () { - return this.info.units; - }, - - /** - * Return the name of the calendar that is commonly used in the given locale. - * - * @returns {string} The name of the calendar commonly used in the locale - */ - getCalendar: function () { - return this.info.calendar; - }, - - /** - * Return the day of week that starts weeks in the current locale. Days are still - * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars - * should be displayed and weeks calculated with the day of week returned from this - * function as the first day of the week. - * - * @returns {number} the day of the week that starts weeks in the current locale. - */ - getFirstDayOfWeek: function () { - return this.info.firstDayOfWeek; - }, - - /** - * Return the day of week that starts weekend in the current locale. Days are still - * numbered the standard way with 0 for Sunday through 6 for Saturday. - * - * @returns {number} the day of the week that starts weeks in the current locale. - */ - getWeekEndStart: function () { - return this.info.weekendStart; - }, - - /** - * Return the day of week that starts weekend in the current locale. Days are still - * numbered the standard way with 0 for Sunday through 6 for Saturday. - * - * @returns {number} the day of the week that starts weeks in the current locale. - */ - getWeekEndEnd: function () { - return this.info.weekendEnd; - }, - - /** - * Return the default time zone for this locale. Many locales span across multiple - * time zones. In this case, the time zone with the largest population is chosen - * to represent the locale. This is obviously not that accurate, but then again, - * this method's return value should only be used as a default anyways. - * @returns {string} the default time zone for this locale. - */ - getTimeZone: function () { - return this.info.timezone; - }, - - /** - * Return the decimal separator for formatted numbers in this locale. - * @returns {string} the decimal separator char - */ - getDecimalSeparator: function () { - return this.info.numfmt.decimalChar; - }, - - /** - * Return the decimal separator for formatted numbers in this locale for native script. - * @returns {string} the decimal separator char - */ - getNativeDecimalSeparator: function () { - return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; - }, - - /** - * Return the separator character used to separate groups of digits on the - * integer side of the decimal character. - * @returns {string} the grouping separator char - */ - getGroupingSeparator: function () { - return this.info.numfmt.groupChar; - }, - - /** - * Return the separator character used to separate groups of digits on the - * integer side of the decimal character for the native script if present other than the default script. - * @returns {string} the grouping separator char - */ - getNativeGroupingSeparator: function () { - return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; - }, - - /** - * Return the minimum number of digits grouped together on the integer side - * for the first (primary) group. - * In western European cultures, groupings are in 1000s, so the number of digits - * is 3. - * @returns {number} the number of digits in a primary grouping, or 0 for no grouping - */ - getPrimaryGroupingDigits: function () { - return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; - }, - - /** - * Return the minimum number of digits grouped together on the integer side - * for the second or more (secondary) group.- 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. *
- * - * In western European cultures, all groupings are by 1000s, so the secondary - * size should be 0 because there is no secondary size. In general, if this - * method returns 0, then all groupings are of the primary size.
- * - * For some other cultures, the first grouping (primary) - * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be - * written as: "1,00,000". - * - * @returns {number} the number of digits in a secondary grouping, or 0 for no - * secondary grouping. - */ - getSecondaryGroupingDigits: function () { - return this.info.numfmt.secgroupSize || 0; - }, - - /** - * Return the format template used to format percentages in this locale. - * @returns {string} the format template for formatting percentages - */ - getPercentageFormat: function () { - return this.info.numfmt.pctFmt; - }, - - /** - * Return the format template used to format percentages in this locale - * with negative amounts. - * @returns {string} the format template for formatting percentages - */ - getNegativePercentageFormat: function () { - return this.info.numfmt.negativepctFmt; - }, - - /** - * Return the symbol used for percentages in this locale. - * @returns {string} the symbol used for percentages in this locale - */ - getPercentageSymbol: function () { - return this.info.numfmt.pctChar || "%"; - }, - - /** - * Return the symbol used for exponential in this locale. - * @returns {string} the symbol used for exponential in this locale - */ - getExponential: function () { - return this.info.numfmt.exponential; - }, - - /** - * Return the symbol used for exponential in this locale for native script. - * @returns {string} the symbol used for exponential in this locale for native script - */ - getNativeExponential: function () { - return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; - }, - - /** - * Return the symbol used for percentages in this locale for native script. - * @returns {string} the symbol used for percentages in this locale for native script - */ - getNativePercentageSymbol: function () { - return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; - - }, - /** - * Return the format template used to format negative numbers in this locale. - * @returns {string} the format template for formatting negative numbers - */ - getNegativeNumberFormat: function () { - return this.info.numfmt.negativenumFmt; - }, - - /** - * Return an object containing the format templates for formatting currencies - * in this locale. The object has a number of properties in it that each are - * a particular style of format. Normally, this contains a "common" and an "iso" - * style, but may contain others in the future. - * @returns {Object} an object containing the format templates for currencies - */ - getCurrencyFormats: function () { - return this.info.numfmt.currencyFormats; - }, - - /** - * Return the currency that is legal in the locale, or which is most commonly - * used in regular commerce. - * @returns {string} the ISO 4217 code for the currency of this locale - */ - getCurrency: function () { - return this.info.currency; - }, - - /** - * Return a string that describes the style of digits used by this locale. - * Possible return values are: - *
- *
- * @returns {string} string that describes the style of digits used in this locale - */ - getDigitsStyle: function () { - if (this.info.numfmt && this.info.numfmt.useNative) { - return "native"; - } - if (typeof(this.info.native_numfmt) !== 'undefined') { - return "optional"; - } - return "western"; - }, - - /** - * Return the digits of the default script if they are defined. - * If not defined, the default should be the regular "Arabic numerals" - * used in the Latin script. (0-9) - * @returns {string|undefined} the digits used in the default script - */ - getDigits: function () { - return this.info.numfmt.digits; - }, - - /** - * Return the digits of the native script if they are defined. - * @returns {string|undefined} the digits used in the default script - */ - getNativeDigits: function () { - return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); - }, - - /** - * If this locale typically uses a different type of rounding for numeric - * formatting other than halfdown, especially for currency, then it can be - * specified in the localeinfo. If the locale uses the default, then this - * method returns undefined. The locale's rounding method overrides the - * rounding method for the currency itself, which can sometimes shared - * between various locales so it is less specific. - * @returns {string} the name of the rounding mode typically used in this - * locale, or "halfdown" if the locale does not override the default - */ - getRoundingMode: function () { - return this.info.numfmt.roundingMode; - }, - - /** - * Return the default script used to write text in the language of this - * locale. Text for most languages is written in only one script, but there - * are some languages where the text can be written in a number of scripts, - * depending on a variety of things such as the region, ethnicity, religion, - * etc. of the author. This method returns the default script for the - * locale, in which the language is most commonly written.- western - uses the regular western 10-based digits 0 through 9 - *
- optional - native 10-based digits exist, but in modern usage, - * this locale most often uses western digits - *
- native - native 10-based native digits exist and are used - * regularly by this locale - *
- custom - uses native digits by default that are not 10-based - *
- * - * The script is returned as an ISO 15924 4-letter code. - * - * @returns {string} the ISO 15924 code for the default script used to write - * text in this locale - */ - getDefaultScript: function() { - return (this.info.scripts) ? this.info.scripts[0] : "Latn"; - }, - - /** - * Return the script used for the current locale. If the current locale - * explicitly defines a script, then this script is returned. If not, then - * the default script for the locale is returned. - * - * @see LocaleInfo.getDefaultScript - * @returns {string} the ISO 15924 code for the script used to write - * text in this locale - */ - getScript: function() { - return this.locale.getScript() || this.getDefaultScript(); - }, - - /** - * Return an array of script codes which are used to write text in the current - * language. Text for most languages is written in only one script, but there - * are some languages where the text can be written in a number of scripts, - * depending on a variety of things such as the region, ethnicity, religion, - * etc. of the author. This method returns an array of script codes in which - * the language is commonly written. - * - * @returns {Array.
} an array of ISO 15924 codes for the scripts used - * to write text in this language - */ - getAllScripts: function() { - return this.info.scripts || ["Latn"]; - }, - - /** - * Return the default style of meridiems used in this locale. Meridiems are - * times of day like AM/PM. In a few locales with some calendars, for example - * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be - * split into different segments than simple AM/PM as in the Gregorian - * calendar. Only a few locales are like that. For most locales, formatting - * a Gregorian date will use the regular Gregorian AM/PM meridiems. - * - * @returns {string} the default meridiems style used in this locale. Possible - * values are "gregorian", "chinese", and "ethiopic" - */ - getMeridiemsStyle: function () { - return this.info.meridiems || "gregorian"; - }, - /** - * Return the default PaperSize information in this locale. - * @returns {string} default PaperSize in this locale - */ - getPaperSize: function () { - return this.info.paperSizes.regular; - }, - /** - * Return the default Delimiter QuotationStart information in this locale. - * @returns {string} default QuotationStart in this locale - */ - getDelimiterQuotationStart: function () { - return this.info.delimiter.quotationStart; - }, - /** - * Return the default Delimiter QuotationEnd information in this locale. - * @returns {string} default QuotationEnd in this locale - */ - getDelimiterQuotationEnd: function () { - return this.info.delimiter.quotationEnd; - } + return this.info["region.name"]; + }, + + /** + * Return whether this locale commonly uses the 12- or the 24-hour clock. + * + * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" + * if the locale commonly uses a 24-hour clock. + */ + getClock: function() { + return this.info.clock; + }, + + /** + * Return the locale that this info object was created with. + * @returns {Locale} The locale spec of the locale used to construct this info instance + */ + getLocale: function () { + return this.locale; + }, + + /** + * Return the name of the measuring system that is commonly used in the given locale. + * Valid values are "uscustomary", "imperial", and "metric". + * + * @returns {string} The name of the measuring system commonly used in the locale + */ + getUnits: function () { + return this.info.units; + }, + + /** + * Return the name of the calendar that is commonly used in the given locale. + * + * @returns {string} The name of the calendar commonly used in the locale + */ + getCalendar: function () { + return this.info.calendar; + }, + + /** + * Return the day of week that starts weeks in the current locale. Days are still + * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars + * should be displayed and weeks calculated with the day of week returned from this + * function as the first day of the week. + * + * @returns {number} the day of the week that starts weeks in the current locale. + */ + getFirstDayOfWeek: function () { + return this.info.firstDayOfWeek; + }, + + /** + * Return the day of week that starts weekend in the current locale. Days are still + * numbered the standard way with 0 for Sunday through 6 for Saturday. + * + * @returns {number} the day of the week that starts weeks in the current locale. + */ + getWeekEndStart: function () { + return this.info.weekendStart; + }, + + /** + * Return the day of week that starts weekend in the current locale. Days are still + * numbered the standard way with 0 for Sunday through 6 for Saturday. + * + * @returns {number} the day of the week that starts weeks in the current locale. + */ + getWeekEndEnd: function () { + return this.info.weekendEnd; + }, + + /** + * Return the default time zone for this locale. Many locales span across multiple + * time zones. In this case, the time zone with the largest population is chosen + * to represent the locale. This is obviously not that accurate, but then again, + * this method's return value should only be used as a default anyways. + * @returns {string} the default time zone for this locale. + */ + getTimeZone: function () { + return this.info.timezone; + }, + + /** + * Return the decimal separator for formatted numbers in this locale. + * @returns {string} the decimal separator char + */ + getDecimalSeparator: function () { + return this.info.numfmt.decimalChar; + }, + + /** + * Return the decimal separator for formatted numbers in this locale for native script. + * @returns {string} the decimal separator char + */ + getNativeDecimalSeparator: function () { + return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; + }, + + /** + * Return the separator character used to separate groups of digits on the + * integer side of the decimal character. + * @returns {string} the grouping separator char + */ + getGroupingSeparator: function () { + return this.info.numfmt.groupChar; + }, + + /** + * Return the separator character used to separate groups of digits on the + * integer side of the decimal character for the native script if present other than the default script. + * @returns {string} the grouping separator char + */ + getNativeGroupingSeparator: function () { + return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; + }, + + /** + * Return the minimum number of digits grouped together on the integer side + * for the first (primary) group. + * In western European cultures, groupings are in 1000s, so the number of digits + * is 3. + * @returns {number} the number of digits in a primary grouping, or 0 for no grouping + */ + getPrimaryGroupingDigits: function () { + return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; + }, + + /** + * Return the minimum number of digits grouped together on the integer side + * for the second or more (secondary) group. + * + * In western European cultures, all groupings are by 1000s, so the secondary + * size should be 0 because there is no secondary size. In general, if this + * method returns 0, then all groupings are of the primary size.
+ * + * For some other cultures, the first grouping (primary) + * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be + * written as: "1,00,000". + * + * @returns {number} the number of digits in a secondary grouping, or 0 for no + * secondary grouping. + */ + getSecondaryGroupingDigits: function () { + return this.info.numfmt.secgroupSize || 0; + }, + + /** + * Return the format template used to format percentages in this locale. + * @returns {string} the format template for formatting percentages + */ + getPercentageFormat: function () { + return this.info.numfmt.pctFmt; + }, + + /** + * Return the format template used to format percentages in this locale + * with negative amounts. + * @returns {string} the format template for formatting percentages + */ + getNegativePercentageFormat: function () { + return this.info.numfmt.negativepctFmt; + }, + + /** + * Return the symbol used for percentages in this locale. + * @returns {string} the symbol used for percentages in this locale + */ + getPercentageSymbol: function () { + return this.info.numfmt.pctChar || "%"; + }, + + /** + * Return the symbol used for exponential in this locale. + * @returns {string} the symbol used for exponential in this locale + */ + getExponential: function () { + return this.info.numfmt.exponential; + }, + + /** + * Return the symbol used for exponential in this locale for native script. + * @returns {string} the symbol used for exponential in this locale for native script + */ + getNativeExponential: function () { + return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; + }, + + /** + * Return the symbol used for percentages in this locale for native script. + * @returns {string} the symbol used for percentages in this locale for native script + */ + getNativePercentageSymbol: function () { + return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; + + }, + /** + * Return the format template used to format negative numbers in this locale. + * @returns {string} the format template for formatting negative numbers + */ + getNegativeNumberFormat: function () { + return this.info.numfmt.negativenumFmt; + }, + + /** + * Return an object containing the format templates for formatting currencies + * in this locale. The object has a number of properties in it that each are + * a particular style of format. Normally, this contains a "common" and an "iso" + * style, but may contain others in the future. + * @returns {Object} an object containing the format templates for currencies + */ + getCurrencyFormats: function () { + return this.info.numfmt.currencyFormats; + }, + + /** + * Return the currency that is legal in the locale, or which is most commonly + * used in regular commerce. + * @returns {string} the ISO 4217 code for the currency of this locale + */ + getCurrency: function () { + return this.info.currency; + }, + + /** + * Return a string that describes the style of digits used by this locale. + * Possible return values are: + *
+ *
+ * @returns {string} string that describes the style of digits used in this locale + */ + getDigitsStyle: function () { + if (this.info.numfmt && this.info.numfmt.useNative) { + return "native"; + } + if (typeof(this.info.native_numfmt) !== 'undefined') { + return "optional"; + } + return "western"; + }, + + /** + * Return the digits of the default script if they are defined. + * If not defined, the default should be the regular "Arabic numerals" + * used in the Latin script. (0-9) + * @returns {string|undefined} the digits used in the default script + */ + getDigits: function () { + return this.info.numfmt.digits; + }, + + /** + * Return the digits of the native script if they are defined. + * @returns {string|undefined} the digits used in the default script + */ + getNativeDigits: function () { + return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); + }, + + /** + * If this locale typically uses a different type of rounding for numeric + * formatting other than halfdown, especially for currency, then it can be + * specified in the localeinfo. If the locale uses the default, then this + * method returns undefined. The locale's rounding method overrides the + * rounding method for the currency itself, which can sometimes shared + * between various locales so it is less specific. + * @returns {string} the name of the rounding mode typically used in this + * locale, or "halfdown" if the locale does not override the default + */ + getRoundingMode: function () { + return this.info.numfmt.roundingMode; + }, + + /** + * Return the default script used to write text in the language of this + * locale. Text for most languages is written in only one script, but there + * are some languages where the text can be written in a number of scripts, + * depending on a variety of things such as the region, ethnicity, religion, + * etc. of the author. This method returns the default script for the + * locale, in which the language is most commonly written.- western - uses the regular western 10-based digits 0 through 9 + *
- optional - native 10-based digits exist, but in modern usage, + * this locale most often uses western digits + *
- native - native 10-based native digits exist and are used + * regularly by this locale + *
- custom - uses native digits by default that are not 10-based + *
+ * + * The script is returned as an ISO 15924 4-letter code. + * + * @returns {string} the ISO 15924 code for the default script used to write + * text in this locale + */ + getDefaultScript: function() { + return (this.info.scripts) ? this.info.scripts[0] : "Latn"; + }, + + /** + * Return the script used for the current locale. If the current locale + * explicitly defines a script, then this script is returned. If not, then + * the default script for the locale is returned. + * + * @see LocaleInfo.getDefaultScript + * @returns {string} the ISO 15924 code for the script used to write + * text in this locale + */ + getScript: function() { + return this.locale.getScript() || this.getDefaultScript(); + }, + + /** + * Return an array of script codes which are used to write text in the current + * language. Text for most languages is written in only one script, but there + * are some languages where the text can be written in a number of scripts, + * depending on a variety of things such as the region, ethnicity, religion, + * etc. of the author. This method returns an array of script codes in which + * the language is commonly written. + * + * @returns {Array.
} an array of ISO 15924 codes for the scripts used + * to write text in this language + */ + getAllScripts: function() { + return this.info.scripts || ["Latn"]; + }, + + /** + * Return the default style of meridiems used in this locale. Meridiems are + * times of day like AM/PM. In a few locales with some calendars, for example + * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be + * split into different segments than simple AM/PM as in the Gregorian + * calendar. Only a few locales are like that. For most locales, formatting + * a Gregorian date will use the regular Gregorian AM/PM meridiems. + * + * @returns {string} the default meridiems style used in this locale. Possible + * values are "gregorian", "chinese", and "ethiopic" + */ + getMeridiemsStyle: function () { + return this.info.meridiems || "gregorian"; + }, + /** + * Return the default PaperSize information in this locale. + * @returns {string} default PaperSize in this locale + */ + getPaperSize: function () { + return this.info.paperSizes.regular; + }, + /** + * Return the default Delimiter QuotationStart information in this locale. + * @returns {string} default QuotationStart in this locale + */ + getDelimiterQuotationStart: function () { + return this.info.delimiter.quotationStart; + }, + /** + * Return the default Delimiter QuotationEnd information in this locale. + * @returns {string} default QuotationEnd in this locale + */ + getDelimiterQuotationEnd: function () { + return this.info.delimiter.quotationEnd; + } }; module.exports = LocaleInfo; diff --git a/js/lib/LocaleMatcher.js b/js/lib/LocaleMatcher.js index 4f2af9a69c..b2bec001e9 100644 --- a/js/lib/LocaleMatcher.js +++ b/js/lib/LocaleMatcher.js @@ -19,15 +19,15 @@ // !data localematch -var ilib = require("./ilib.js"); +var ilib = require("../index"); var Utils = require("./Utils.js"); var Locale = require("./Locale.js"); var componentWeights = [ - 0.5, // language - 0.2, // script - 0.25, // region - 0.05 // variant + 0.5, // language + 0.2, // script + 0.25, // region + 0.05 // variant ]; /** @@ -65,69 +65,69 @@ var componentWeights = [ * @param {Object} options parameters to initialize this matcher */ var LocaleMatcher = function(options) { - var sync = true, - loadParams = undefined; - - this.locale = new Locale(); - - if (options) { - if (typeof(options.locale) !== 'undefined') { - this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; - } - - if (typeof(options.sync) !== 'undefined') { - sync = !!options.sync; - } - - if (typeof(options.loadParams) !== 'undefined') { - loadParams = options.loadParams; - } - } - - if (typeof(ilib.data.localematch) === 'undefined') { - Utils.loadData({ - object: "LocaleMatcher", - locale: "-", - name: "localematch.json", - sync: sync, - loadParams: loadParams, - callback: ilib.bind(this, function (info) { - if (!info) { - info = {}; - } - /** @type {Object. } */ - this.info = info; - if (options && typeof(options.onLoad) === 'function') { - options.onLoad(this); - } - }) - }); - } else { - this.info = ilib.data.localematch; - if (options && typeof(options.onLoad) === 'function') { + var sync = true, + loadParams = undefined; + + this.locale = new Locale(); + + if (options) { + if (typeof(options.locale) !== 'undefined') { + this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; + } + + if (typeof(options.sync) !== 'undefined') { + sync = !!options.sync; + } + + if (typeof(options.loadParams) !== 'undefined') { + loadParams = options.loadParams; + } + } + + if (typeof(ilib.data.localematch) === 'undefined') { + Utils.loadData({ + object: "LocaleMatcher", + locale: "-", + name: "localematch.json", + sync: sync, + loadParams: loadParams, + callback: ilib.bind(this, function (info) { + if (!info) { + info = {}; + } + /** @type {Object. } */ + this.info = info; + if (options && typeof(options.onLoad) === 'function') { + options.onLoad(this); + } + }) + }); + } else { + this.info = ilib.data.localematch; + if (options && typeof(options.onLoad) === 'function') { options.onLoad(this); } - } + } }; LocaleMatcher.prototype = { - /** - * Return the locale used to construct this instance. - * @return {Locale|undefined} the locale for this matcher - */ - getLocale: function() { - return this.locale; - }, - - /** - * @private - * Do the work - */ - _getLikelyLocale: function(locale) { - // already full specified - if (locale.language && locale.script && locale.region) return locale; - + /** + * Return the locale used to construct this instance. + * @return {Locale|undefined} the locale for this matcher + */ + getLocale: function() { + return this.locale; + }, + + /** + * @private + * Do the work + */ + _getLikelyLocale: function(locale) { + // already full specified + if (locale.language && locale.script && locale.region) return locale; + if (typeof(this.info.likelyLocales[locale.getSpec()]) === 'undefined') { // try various partials before giving up var partial = this.info.likelyLocales[new Locale(locale.language, undefined, locale.region).getSpec()]; @@ -152,179 +152,179 @@ LocaleMatcher.prototype = { } return new Locale(this.info.likelyLocales[locale.getSpec()]); - }, - - /** - * Return an Locale instance that is fully specified based on partial information - * given to the constructor of this locale matcher instance. For example, if the locale - * spec given to this locale matcher instance is simply "ru" (for the Russian language), - * then it will fill in the missing region and script tags and return a locale with - * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). - * Any one or two of the language, script, or region parts may be left unspecified, - * and the other one or two parts will be filled in automatically. If this - * class has no information about the given locale, then the locale of this - * locale matcher instance is returned unchanged. - * - * @returns {Locale} the most likely completion of the partial locale given - * to the constructor of this locale matcher instance - */ - getLikelyLocale: function () { - return this._getLikelyLocale(this.locale); - }, - - /** - * Return the degree that the given locale matches the current locale of this - * matcher. This method returns an integer from 0 to 100. A value of 100 is - * a 100% match, meaning that the two locales are exactly equivalent to each - * other. (eg. "ja-JP" and "ja-JP") A value of 0 means that there 0% match or - * that the two locales have nothing in common. (eg. "en-US" and "ja-JP") - * - * Locale matching is not the same as equivalence, as the degree of matching - * is returned. (See Locale.equals for equivalence.)
- * - * The match score is calculated based on matching the 4 locale components, - * weighted by importance: - * - *
- *
- * - * The score is affected by the following things: - * - *- language - this accounts for 50% of the match score - *
- region - accounts for 25% of the match score - *
- script - accounts for 20% of the match score - *
- variant - accounts for 5% of the match score - *
- *
- * - * The intention of this method is that it can be used to determine - * compatibility of locales. For example, when a user signs up for an - * account on a web site, the locales that the web site supports and - * the locale of the user's browser may differ, and the site needs to - * pick the best locale to show the user. Let's say the - * web site supports a selection of European languages such as "it-IT", - * "fr-FR", "de-DE", and "en-GB". The user's - * browser may be set to "it-CH". The web site code can then match "it-CH" - * against each of the supported locales to find the one with the - * highest score. In - * this case, the best match would be "it-IT" because it shares a - * language and script in common with "it-CH" and differs only in the region - * component. It is not a 100% match, but it is pretty good. The web site - * may decide if the match scores all fall - * below a chosen threshold (perhaps 50%?), it should show the user the - * default language "en-GB", because that is probably a better choice - * than any other supported locale.- A large language score is given when the language components of the locales - * match exactly. - *
- Higher language scores are given when the languages are linguistically - * close to each other, such as dialects. - *
- A small score is given when two languages are in the same - * linguistic family, but one is not a dialect of the other, such as German - * and Dutch. - *
- A large region score is given when two locales share the same region. - *
- A smaller region score is given when one region is contained within - * another. For example, Hong Kong is part of China, so a moderate score is - * given instead of a full score. - *
- A small score is given if two regions are geographically close to - * each other or are tied by history. For example, Ireland and Great Britain - * are both adjacent and tied by history, so they receive a moderate score. - *
- A high script score is given if the two locales share the same script. - * The legibility of a common script means that there is some small kinship of the - * different languages. - *
- A high variant score is given if the two locales share the same - * variant. Full score is given when both locales have no variant at all. - *
- Locale components that are unspecified in both locales are given high - * scores. - *
- Locales where a particular locale component is missing in only one - * locale can still match when the default for that locale component matches - * the component in the other locale. The - * default value for the missing component is determined using the likely locales - * data. (See getLikelyLocale()) For example, "en-US" and "en-Latn-US" receive - * a high script score because the default script for "en" is "Latn". - *
- * - * @param {Locale} locale the other locale to match against the current one - * @return {number} an integer from 0 to 100 that indicates the degree to - * which these locales match each other - */ - match: function(locale) { - var other = new Locale(locale); - var scores = [0, 0, 0, 0]; - var thisfull, otherfull, i; - - if (this.locale.language === other.language) { - scores[0] = 100; - } else { - if (!this.locale.language || !other.language) { - // check for default language - thisfull = this.getLikelyLocale(); - otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); - if (thisfull.language === otherfull.language) { - scores[0] = 100; - } - } else { - // check for macro languages - var mlthis = this.info.macroLanguagesReverse[this.locale.language] || this.locale.language; - var mlother = this.info.macroLanguagesReverse[other.language] || other.language; - if (mlthis === mlother) { - scores[0] = 90; - } else { - // check for mutual intelligibility - var pair = this.locale.language + "-" + other.language; - scores[0] = this.info.mutualIntelligibility[pair] || 0; - } - } - } - - if (this.locale.script === other.script) { - scores[1] = 100; - } else { - if (!this.locale.script || !other.script) { - // check for default script - thisfull = this.locale.script ? this.locale : new Locale(this.info.likelyLocales[this.locale.language]); - otherfull = other.script ? other : new Locale(this.info.likelyLocales[other.language]); - if (thisfull.script === otherfull.script) { - scores[1] = 100; - } - } - } - - if (this.locale.region === other.region) { - scores[2] = 100; - } else { - if (!this.locale.region || !other.region) { - // check for default region - thisfull = this.getLikelyLocale(); - otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); - if (thisfull.region === otherfull.region) { - scores[2] = 100; - } - } else { - // check for containment - var containers = this.info.territoryContainmentReverse[this.locale.region] || []; - // end at 1 because 0 is "001" which is "the whole world" -- which is not useful - for (i = containers.length-1; i > 0; i--) { - var container = this.info.territoryContainment[containers[i]]; - if (container && container.indexOf(other.region) > -1) { - // same area only accounts for 20% of the region score - scores[2] = ((i+1) * 100 / containers.length) * 0.2; - break; - } - } - } - } - - if (this.locale.variant === other.variant) { - scores[3] = 100; - } - - var total = 0; - - for (i = 0; i < 4; i++) { - total += scores[i] * componentWeights[i]; - } - - return Math.round(total); - }, + }, + + /** + * Return an Locale instance that is fully specified based on partial information + * given to the constructor of this locale matcher instance. For example, if the locale + * spec given to this locale matcher instance is simply "ru" (for the Russian language), + * then it will fill in the missing region and script tags and return a locale with + * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). + * Any one or two of the language, script, or region parts may be left unspecified, + * and the other one or two parts will be filled in automatically. If this + * class has no information about the given locale, then the locale of this + * locale matcher instance is returned unchanged. + * + * @returns {Locale} the most likely completion of the partial locale given + * to the constructor of this locale matcher instance + */ + getLikelyLocale: function () { + return this._getLikelyLocale(this.locale); + }, + + /** + * Return the degree that the given locale matches the current locale of this + * matcher. This method returns an integer from 0 to 100. A value of 100 is + * a 100% match, meaning that the two locales are exactly equivalent to each + * other. (eg. "ja-JP" and "ja-JP") A value of 0 means that there 0% match or + * that the two locales have nothing in common. (eg. "en-US" and "ja-JP")
+ * + * Locale matching is not the same as equivalence, as the degree of matching + * is returned. (See Locale.equals for equivalence.)
+ * + * The match score is calculated based on matching the 4 locale components, + * weighted by importance: + * + *
+ *
+ * + * The score is affected by the following things: + * + *- language - this accounts for 50% of the match score + *
- region - accounts for 25% of the match score + *
- script - accounts for 20% of the match score + *
- variant - accounts for 5% of the match score + *
+ *
+ * + * The intention of this method is that it can be used to determine + * compatibility of locales. For example, when a user signs up for an + * account on a web site, the locales that the web site supports and + * the locale of the user's browser may differ, and the site needs to + * pick the best locale to show the user. Let's say the + * web site supports a selection of European languages such as "it-IT", + * "fr-FR", "de-DE", and "en-GB". The user's + * browser may be set to "it-CH". The web site code can then match "it-CH" + * against each of the supported locales to find the one with the + * highest score. In + * this case, the best match would be "it-IT" because it shares a + * language and script in common with "it-CH" and differs only in the region + * component. It is not a 100% match, but it is pretty good. The web site + * may decide if the match scores all fall + * below a chosen threshold (perhaps 50%?), it should show the user the + * default language "en-GB", because that is probably a better choice + * than any other supported locale.- A large language score is given when the language components of the locales + * match exactly. + *
- Higher language scores are given when the languages are linguistically + * close to each other, such as dialects. + *
- A small score is given when two languages are in the same + * linguistic family, but one is not a dialect of the other, such as German + * and Dutch. + *
- A large region score is given when two locales share the same region. + *
- A smaller region score is given when one region is contained within + * another. For example, Hong Kong is part of China, so a moderate score is + * given instead of a full score. + *
- A small score is given if two regions are geographically close to + * each other or are tied by history. For example, Ireland and Great Britain + * are both adjacent and tied by history, so they receive a moderate score. + *
- A high script score is given if the two locales share the same script. + * The legibility of a common script means that there is some small kinship of the + * different languages. + *
- A high variant score is given if the two locales share the same + * variant. Full score is given when both locales have no variant at all. + *
- Locale components that are unspecified in both locales are given high + * scores. + *
- Locales where a particular locale component is missing in only one + * locale can still match when the default for that locale component matches + * the component in the other locale. The + * default value for the missing component is determined using the likely locales + * data. (See getLikelyLocale()) For example, "en-US" and "en-Latn-US" receive + * a high script score because the default script for "en" is "Latn". + *
+ * + * @param {Locale} locale the other locale to match against the current one + * @return {number} an integer from 0 to 100 that indicates the degree to + * which these locales match each other + */ + match: function(locale) { + var other = new Locale(locale); + var scores = [0, 0, 0, 0]; + var thisfull, otherfull, i; + + if (this.locale.language === other.language) { + scores[0] = 100; + } else { + if (!this.locale.language || !other.language) { + // check for default language + thisfull = this.getLikelyLocale(); + otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); + if (thisfull.language === otherfull.language) { + scores[0] = 100; + } + } else { + // check for macro languages + var mlthis = this.info.macroLanguagesReverse[this.locale.language] || this.locale.language; + var mlother = this.info.macroLanguagesReverse[other.language] || other.language; + if (mlthis === mlother) { + scores[0] = 90; + } else { + // check for mutual intelligibility + var pair = this.locale.language + "-" + other.language; + scores[0] = this.info.mutualIntelligibility[pair] || 0; + } + } + } + + if (this.locale.script === other.script) { + scores[1] = 100; + } else { + if (!this.locale.script || !other.script) { + // check for default script + thisfull = this.locale.script ? this.locale : new Locale(this.info.likelyLocales[this.locale.language]); + otherfull = other.script ? other : new Locale(this.info.likelyLocales[other.language]); + if (thisfull.script === otherfull.script) { + scores[1] = 100; + } + } + } + + if (this.locale.region === other.region) { + scores[2] = 100; + } else { + if (!this.locale.region || !other.region) { + // check for default region + thisfull = this.getLikelyLocale(); + otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); + if (thisfull.region === otherfull.region) { + scores[2] = 100; + } + } else { + // check for containment + var containers = this.info.territoryContainmentReverse[this.locale.region] || []; + // end at 1 because 0 is "001" which is "the whole world" -- which is not useful + for (i = containers.length-1; i > 0; i--) { + var container = this.info.territoryContainment[containers[i]]; + if (container && container.indexOf(other.region) > -1) { + // same area only accounts for 20% of the region score + scores[2] = ((i+1) * 100 / containers.length) * 0.2; + break; + } + } + } + } + + if (this.locale.variant === other.variant) { + scores[3] = 100; + } + + var total = 0; + + for (i = 0; i < 4; i++) { + total += scores[i] * componentWeights[i]; + } + + return Math.round(total); + }, /** * Return the macrolanguage associated with this locale. If the diff --git a/js/lib/MassUnit.js b/js/lib/MassUnit.js index d96cdc20b7..316dfdb332 100644 --- a/js/lib/MassUnit.js +++ b/js/lib/MassUnit.js @@ -34,8 +34,8 @@ var Measurement = require("./Measurement.js"); * the construction of this instance */ var MassUnit = function (options) { - this.unit = "gram"; - this.amount = 0; + this.unit = "gram"; + this.amount = 0; this.ratios = MassUnit.ratios; this.aliases = MassUnit.aliases; @@ -151,7 +151,7 @@ MassUnit.systems = { * @return {string} the name of the type of this measurement */ MassUnit.prototype.getMeasure = function() { - return "mass"; + return "mass"; }; MassUnit.aliases = { diff --git a/js/lib/MathUtils.js b/js/lib/MathUtils.js index bf600864e1..46f393cdac 100644 --- a/js/lib/MathUtils.js +++ b/js/lib/MathUtils.js @@ -27,13 +27,13 @@ var MathUtils = {}; * @return {number} -1 if the number is negative, and 1 otherwise */ MathUtils.signum = function (num) { - var n = num; - if (typeof(num) === 'string') { - n = parseInt(num, 10); - } else if (typeof(num) !== 'number') { - return 1; - } - return (n < 0) ? -1 : 1; + var n = num; + if (typeof(num) === 'string') { + n = parseInt(num, 10); + } else if (typeof(num) !== 'number') { + return 1; + } + return (n < 0) ? -1 : 1; }; /** @@ -43,7 +43,7 @@ MathUtils.signum = function (num) { * @return {number} rounded number */ MathUtils.floor = function (num) { - return Math.floor(num); + return Math.floor(num); }; /** @@ -53,7 +53,7 @@ MathUtils.floor = function (num) { * @return {number} rounded number */ MathUtils.ceiling = function (num) { - return Math.ceil(num); + return Math.ceil(num); }; /** @@ -63,7 +63,7 @@ MathUtils.ceiling = function (num) { * @return {number} rounded number */ MathUtils.down = function (num) { - return (num < 0) ? Math.ceil(num) : Math.floor(num); + return (num < 0) ? Math.ceil(num) : Math.floor(num); }; /** @@ -73,7 +73,7 @@ MathUtils.down = function (num) { * @return {number} rounded number */ MathUtils.up = function (num) { - return (num < 0) ? Math.floor(num) : Math.ceil(num); + return (num < 0) ? Math.floor(num) : Math.ceil(num); }; /** @@ -83,7 +83,7 @@ MathUtils.up = function (num) { * @return {number} rounded number */ MathUtils.halfup = function (num) { - return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); + return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); }; /** @@ -93,7 +93,7 @@ MathUtils.halfup = function (num) { * @return {number} rounded number */ MathUtils.halfdown = function (num) { - return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); + return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); }; /** @@ -103,7 +103,7 @@ MathUtils.halfdown = function (num) { * @return {number} rounded number */ MathUtils.halfeven = function (num) { - return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); + return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); }; /** @@ -113,7 +113,7 @@ MathUtils.halfeven = function (num) { * @return {number} rounded number */ MathUtils.halfodd = function (num) { - return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); + return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); }; /** @@ -129,11 +129,11 @@ MathUtils.halfodd = function (num) { * @return the remainder of dividing the dividend by the modulus. */ MathUtils.mod = function (dividend, modulus) { - if (modulus == 0) { - return 0; - } - var x = dividend % modulus; - return (x < 0) ? x + modulus : x; + if (modulus == 0) { + return 0; + } + var x = dividend % modulus; + return (x < 0) ? x + modulus : x; }; /** @@ -151,11 +151,11 @@ MathUtils.mod = function (dividend, modulus) { * @return the remainder of dividing the dividend by the modulus. */ MathUtils.amod = function (dividend, modulus) { - if (modulus == 0) { - return 0; - } - var x = dividend % modulus; - return (x <= 0) ? x + modulus : x; + if (modulus == 0) { + return 0; + } + var x = dividend % modulus; + return (x <= 0) ? x + modulus : x; }; /** diff --git a/js/lib/Measurement.js b/js/lib/Measurement.js index 367ce3d3ce..1a35b11306 100644 --- a/js/lib/Measurement.js +++ b/js/lib/Measurement.js @@ -152,7 +152,7 @@ Measurement.prototype = { if (JSUtils.indexOf(this.systems.uscustomary, this.unit) > -1) { return "uscustomary"; } - + if (JSUtils.indexOf(this.systems.imperial, this.unit) > -1) { return "imperial"; } diff --git a/js/lib/MeasurementFactory.js b/js/lib/MeasurementFactory.js index fe5c778ea5..74156379cd 100644 --- a/js/lib/MeasurementFactory.js +++ b/js/lib/MeasurementFactory.js @@ -74,13 +74,13 @@ var Measurement = require("./Measurement.js"); * and stored as that number of base units. * For example, if an instance is constructed with 1 kg, this will be converted * automatically into 1000 g, as grams are the base unit and kg is merely a - * commonly used scale of grams. + * commonly used scale of grams. *
- * + * *
* 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: - * + * * *
* - * 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.- 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. *
* * 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: - * + * *
- *
- * - * + * + * * @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.- 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. *
- * - * 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: - * + * *
*
- * + * * @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.- 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. *
, - * 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: - * - * - *
- * - * @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: - * + * *- "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 + *
- *
* * 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.- 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 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: - * + * * - *
* * 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.- 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 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"); *
* * 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}. *- 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. *
* * 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: - * - * - *
+ * + * 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.- 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: + * + * + *
- * - * 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.- 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. - *
- * - * Example: a subscriber number of "48773" in the US would get formatted as: - * - *
- *
- * - * 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.- 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) - *
} 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; - } + * + * + * Example: a subscriber number of "48773" in the US would get formatted as: + * + *
+ *
+ * + * 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.- 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) + *
} 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: - * + * *
- *
- * + * * 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 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 + *
- 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 + * + *
- 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. - * + * 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 + * 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 + * + *
- 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. *
- * + * * 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: - * + * *
*
- * + * * @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), + * 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. *
- *
- * - * @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.- 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. - *
- * - * 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: + * + *
+ *
* - * @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.- 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. + *
+ * + * 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: - * + * * *
- * + * * 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.- 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. *
- * - * 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: - * + * *
*
- * + * * The following rules determine how the number is parsed: - * + * *- 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 *
*
- * + * * Example: parsing the number "+49 02101345345-78" will give the following properties in the * resulting phone number instance: - * + * *- 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. *
- 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. *
- 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. - *
- If the appropriate locale cannot be easily determined, default to using the rules + *
- If the appropriate locale cannot be easily determined, default to using the rules * for the current user's region. *
* { * 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: - * + * **
- * + * * @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: - * + * *- NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations *
- UK @@ -151,245 +151,245 @@ var PhoneHandlerFactory = require("./PhoneHandlerFactory.js"); *
- Russia *
- Brazil *
- *
- * + * * 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: * *- mcc - Mobile Country Code, which identifies the country where the phone is currently receiving + *
- mcc - Mobile Country Code, which identifies the country where the phone is currently receiving * service. - *
- mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone - *
- msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on + *
- mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone + *
- 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. *
- *
@@ -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.- 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. *
- * - * 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: - * - *
- *
- * - * The values of the following fields matter if they do not match: - * - *- vsc - *
- iddPrefix - *
- cic - *
- trunkAccess - *
- *
- * - * @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.- 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 - *
- * - * 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: - * - *
- *
- * - * The following are a list of options that control the behaviour of the normalization: - * - *- 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. - *
- *
- * - * 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).- 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: - *
- *
- * 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. - *- 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 - *
- 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. - *
- * - * This function performs the following types of normalizations with assisted - * dialling turned on: - * - *
- *
- * - * This function performs the following types of normalization with assisted - * dialling turned off: - * - *- 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 - *
- 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 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.- 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. - *
+ * + * 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: + * + *
+ *
+ * + * The values of the following fields matter if they do not match: + * + *- vsc + *
- iddPrefix + *
- cic + *
- trunkAccess + *
+ *
+ * + * @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.- 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 + *
+ * + * 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: + * + *
+ *
+ * + * The following are a list of options that control the behaviour of the normalization: + * + *- 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. + *
+ *
+ * + * 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).- 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: + *
+ *
+ * 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. + *- 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 + *
- 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. + *
+ * + * This function performs the following types of normalizations with assisted + * dialling turned on: + * + *
+ *
+ * + * This function performs the following types of normalization with assisted + * dialling turned off: + * + *- 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 + *
- 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 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: - * + * *- 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. + *
- *
* * 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.- 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 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: - * + * *
*
- * - * 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.- 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: *
*
* The default behaviour is the same as before, which is to return the source string * unchanged. - * - *- 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 *
- 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 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): - * + * *
*
- * + * * 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.- language *
- region @@ -120,65 +120,65 @@ var IString = require("./IString.js"); *
- language_region_variant *
- language_script_region_variant *
- * + * 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: - * - *
- *
- * - * 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.- "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. - *
- * - * 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: + * + *
+ *
+ * + * 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.- "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. + *
+ * + * 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: - * - *
- *
- * - * For dynamically loaded data, the code attempts to load the same sequence as - * above, but with slash path separators instead of underscores.- language - *
- region - *
- language_script - *
- language_region - *
- region_variant - *
- language_script_region - *
- language_region_variant - *
- language_script_region_variant - *
- * - * 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: + * + *
+ *
+ * + * For dynamically loaded data, the code attempts to load the same sequence as + * above, but with slash path separators instead of underscores.- language + *
- region + *
- language_script + *
- language_region + *
- region_variant + *
- language_script_region + *
- language_region_variant + *
- language_script_region_variant + *
+ * + * 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: - * + * *
- *
- * - * + * + * * @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.- 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. *
- * + * 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: - * + * *
- *
- * - * + * + * * @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: - * + * *- 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. *
- *
* * 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.- 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 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: - * + * *
- *
- * + * * 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.- 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. *
- * + * * When the id "local" is given or the offset option is specified, this class will * have the following behaviours: *
*
- * - * 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.- 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 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: - * + * *
- *
- * + * * @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: - * + * *- standard - returns the 3 to 5 letter abbreviation of the time zone name such + *
- 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" *
- rfc822 - returns an RFC 822 style time zone specifier, which specifies more * explicitly what the offset is from UTC *
- long - returns the long name of the zone in English *
*
- * - * 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.- shared data (usually English) *
- data for language @@ -167,10 +167,10 @@ Utils.getSublocales = function(locale) { *
- data for language + region + script *
- data for language + region + script + variant *
- * + * * 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");