libphonenumber-js
Advanced tools
Comparing version 0.1.9 to 0.1.10
@@ -21,2 +21,3 @@ 'use strict'; | ||
exports.count_occurences = count_occurences; | ||
exports.repeat = repeat; | ||
@@ -37,5 +38,5 @@ var _metadata = require('../metadata.min'); | ||
// The digits that have not been entered yet will be represented by a \u2008, | ||
// the punctuation space. | ||
var DIGIT_PLACEHOLDER = ' '; // This is an enhanced port of Google Android `libphonenumber`'s | ||
// Used in phone number format template creation. | ||
// Could be any digit, I guess. | ||
var DUMMY_DIGIT = '9'; // This is an enhanced port of Google Android `libphonenumber`'s | ||
// `asyoutypeformatter.js` of 17th November, 2016. | ||
@@ -45,2 +46,12 @@ // | ||
var DUMMY_DIGIT_MATCHER = new RegExp(DUMMY_DIGIT, 'g'); | ||
// I don't know why is it exactly `15` | ||
var LONGEST_NATIONAL_PHONE_NUMBER_LENGTH = 15; | ||
// Create a phone number consisting only of the digit 9 that matches the | ||
// `number_pattern` by applying the pattern to the "longest phone number" string. | ||
var LONGEST_DUMMY_PHONE_NUMBER = repeat(DUMMY_DIGIT, LONGEST_NATIONAL_PHONE_NUMBER_LENGTH); | ||
// The digits that have not been entered yet will be represented by a \u2008, | ||
// the punctuation space. | ||
var DIGIT_PLACEHOLDER = ' '; | ||
var DIGIT_PLACEHOLDER_MATCHER = new RegExp(DIGIT_PLACEHOLDER); | ||
@@ -73,3 +84,3 @@ var DIGIT_PLACEHOLDER_MATCHER_GLOBAL = new RegExp(DIGIT_PLACEHOLDER, 'g'); | ||
var VALID_INCOMPLETE_PHONE_NUMBER = '[' + _parse.PLUS_CHARS + ']{0,1}' + '[' + _parse.VALID_PUNCTUATION + _parse.VALID_DIGITS + ']+'; | ||
var VALID_INCOMPLETE_PHONE_NUMBER = '[' + _parse.PLUS_CHARS + ']{0,1}' + '[' + _parse.VALID_PUNCTUATION + _parse.VALID_DIGITS + ']*'; | ||
@@ -94,33 +105,20 @@ var VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i'); | ||
value: function input(text) { | ||
// this.original_input += text | ||
// Parse input | ||
var extracted_number = (0, _parse.extract_formatted_phone_number)(text, function (number) { | ||
return (0, _common.matches_entirely)(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, number); | ||
}); | ||
var extracted_number = (0, _parse.extract_formatted_phone_number)(text); | ||
var _parse_phone_number = (0, _parse.parse_phone_number)(extracted_number); | ||
var number = _parse_phone_number.number; | ||
var is_international = _parse_phone_number.is_international; | ||
// Special case for just the leading '+' | ||
if (!extracted_number && text.indexOf('+') >= 0) { | ||
is_international = true; | ||
// Special case for a lone '+' sign | ||
// since it's not considered a possible phone number. | ||
if (!extracted_number) { | ||
if (text.indexOf('+') >= 0) { | ||
extracted_number = '+'; | ||
} | ||
} | ||
var parsed_input = ''; | ||
if (is_international) { | ||
parsed_input += '+'; | ||
// Validate possible first part of a phone number | ||
if (!(0, _common.matches_entirely)(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, extracted_number)) { | ||
return this.current_output; | ||
} | ||
if (number) { | ||
parsed_input += number; | ||
} | ||
// Feed the parsed input character-by-character | ||
var _iteratorNormalCompletion = true; | ||
@@ -131,3 +129,3 @@ var _didIteratorError = false; | ||
try { | ||
for (var _iterator = (0, _getIterator3.default)(parsed_input), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
for (var _iterator = (0, _getIterator3.default)((0, _parse.parse_phone_number)(extracted_number)), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var character = _step.value; | ||
@@ -261,3 +259,3 @@ | ||
// then return the formatted number so far. | ||
if (this.current_format) { | ||
if (national_number_formatted_with_previous_format) { | ||
return this.full_phone_number(national_number_formatted_with_previous_format); | ||
@@ -304,3 +302,3 @@ } | ||
this.current_format = undefined; | ||
this.chosen_format = undefined; | ||
@@ -310,2 +308,3 @@ this.last_match_position = 0; | ||
this.formatting_template = undefined; | ||
this.partially_populated_formatting_template = undefined; | ||
@@ -546,3 +545,3 @@ this.national_prefix_is_part_of_formatting_template = false; | ||
// and is still possible, then stick to it. | ||
if (this.current_format === format) { | ||
if (this.chosen_format === format) { | ||
return; | ||
@@ -555,3 +554,3 @@ } | ||
if (this.create_formatting_template(format)) { | ||
this.current_format = format; | ||
this.chosen_format = format; | ||
@@ -613,24 +612,25 @@ // With a new formatting template, the matched position | ||
// Get a formatting template which can be used to efficiently format a | ||
// partial number where digits are added one by one. | ||
// Get a formatting template which can be used to efficiently format | ||
// a partial number where digits are added one by one. | ||
// Create a phone number consisting only of the digit 9 that matches the | ||
// `number_pattern` by applying the pattern to the "longest phone number" string. | ||
var longest_phone_number = '999999999999999'; | ||
// This match will always succeed, | ||
// because the "longest dummy phone number" | ||
// has enough length to accomodate any possible | ||
// national phone number format pattern. | ||
var dummy_phone_number_matching_format_pattern = LONGEST_DUMMY_PHONE_NUMBER.match(number_pattern)[0]; | ||
var matches = longest_phone_number.match(number_pattern); | ||
// This match will always succeed | ||
var phone_number = matches[0]; | ||
// No formatting template can be created if the number of digits entered so | ||
// far is longer than the maximum the current formatting rule can accommodate. | ||
if (phone_number.length < this.national_number.length) { | ||
// If the national number entered is too long | ||
// for any phone number format, then abort. | ||
if (this.national_number.length > dummy_phone_number_matching_format_pattern.length) { | ||
return; | ||
} | ||
return this.formatting_template = phone_number | ||
// Formats the number according to numberFormat | ||
// Create formatting template for this phone number format | ||
this.formatting_template = dummy_phone_number_matching_format_pattern | ||
// Format the dummy phone number according to the format | ||
.replace(new RegExp(number_pattern, 'g'), number_format) | ||
// Replaces each digit with character DIGIT_PLACEHOLDER | ||
.replace(new RegExp('9', 'g'), DIGIT_PLACEHOLDER); | ||
// Replace each dummy digit with a DIGIT_PLACEHOLDER | ||
.replace(DUMMY_DIGIT_MATCHER, DIGIT_PLACEHOLDER); | ||
return this.partially_populated_formatting_template = this.formatting_template; | ||
} | ||
@@ -643,9 +643,9 @@ }, { | ||
// and return the formatted digits so far. | ||
if (this.formatting_template && this.formatting_template.slice(this.last_match_position + 1).search(DIGIT_PLACEHOLDER_MATCHER) >= 0) { | ||
var digit_pattern_start = this.formatting_template.search(DIGIT_PLACEHOLDER_MATCHER); | ||
this.formatting_template = this.formatting_template.replace(DIGIT_PLACEHOLDER_MATCHER, digit); | ||
if (this.chosen_format && this.partially_populated_formatting_template.slice(this.last_match_position + 1).search(DIGIT_PLACEHOLDER_MATCHER) >= 0) { | ||
var digit_pattern_start = this.partially_populated_formatting_template.search(DIGIT_PLACEHOLDER_MATCHER); | ||
this.partially_populated_formatting_template = this.partially_populated_formatting_template.replace(DIGIT_PLACEHOLDER_MATCHER, digit); | ||
this.last_match_position = digit_pattern_start; | ||
// Return the formatted phone number so far | ||
return close_dangling_braces(this.formatting_template, digit_pattern_start + 1).replace(DIGIT_PLACEHOLDER_MATCHER_GLOBAL, ' '); | ||
return close_dangling_braces(this.partially_populated_formatting_template, digit_pattern_start + 1).replace(DIGIT_PLACEHOLDER_MATCHER_GLOBAL, ' '); | ||
} | ||
@@ -655,7 +655,9 @@ | ||
// Reset the current format flag, | ||
// Reset the current format, | ||
// so that the new format will be chosen | ||
// in a subsequent `this.choose_another_format()` call | ||
// later in code. | ||
this.current_format = undefined; | ||
this.chosen_format = undefined; | ||
this.formatting_template = undefined; | ||
this.partially_populated_formatting_template = undefined; | ||
} | ||
@@ -732,2 +734,23 @@ }, { | ||
} | ||
// Repeats a string (or a symbol) N times. | ||
// http://stackoverflow.com/questions/202605/repeat-string-javascript | ||
function repeat(string, times) { | ||
if (times < 1) { | ||
return ''; | ||
} | ||
var result = ''; | ||
while (times > 1) { | ||
if (times & 1) { | ||
result += string; | ||
} | ||
times >>= 1; | ||
string += string; | ||
} | ||
return result + string; | ||
} | ||
//# sourceMappingURL=as you type.js.map |
@@ -22,2 +22,13 @@ "use strict"; | ||
exports.get_metadata_by_country_phone_code = get_metadata_by_country_phone_code; | ||
exports.get_types = get_types; | ||
exports.get_type_fixed_line = get_type_fixed_line; | ||
exports.get_type_mobile = get_type_mobile; | ||
exports.get_type_toll_free = get_type_toll_free; | ||
exports.get_type_premium_rate = get_type_premium_rate; | ||
exports.get_type_personal_number = get_type_personal_number; | ||
exports.get_type_voice_mail = get_type_voice_mail; | ||
exports.get_type_uan = get_type_uan; | ||
exports.get_type_pager = get_type_pager; | ||
exports.get_type_voip = get_type_voip; | ||
exports.get_type_shared_cost = get_type_shared_cost; | ||
function get_phone_code(country_metadata) { | ||
@@ -101,2 +112,50 @@ return country_metadata[0]; | ||
} | ||
function get_types(country_metadata) { | ||
return country_metadata[9]; | ||
} | ||
function get_type(country_metadata, index) { | ||
return get_types(country_metadata) ? get_types(country_metadata)[index] : undefined; | ||
} | ||
function get_type_fixed_line(country_metadata) { | ||
return get_type(country_metadata, 0); | ||
} | ||
function get_type_mobile(country_metadata) { | ||
return get_type(country_metadata, 1); | ||
} | ||
function get_type_toll_free(country_metadata) { | ||
return get_type(country_metadata, 2); | ||
} | ||
function get_type_premium_rate(country_metadata) { | ||
return get_type(country_metadata, 3); | ||
} | ||
function get_type_personal_number(country_metadata) { | ||
return get_type(country_metadata, 4); | ||
} | ||
function get_type_voice_mail(country_metadata) { | ||
return get_type(country_metadata, 5); | ||
} | ||
function get_type_uan(country_metadata) { | ||
return get_type(country_metadata, 6); | ||
} | ||
function get_type_pager(country_metadata) { | ||
return get_type(country_metadata, 7); | ||
} | ||
function get_type_voip(country_metadata) { | ||
return get_type(country_metadata, 8); | ||
} | ||
function get_type_shared_cost(country_metadata) { | ||
return get_type(country_metadata, 9); | ||
} | ||
//# sourceMappingURL=metadata.js.map |
@@ -19,3 +19,2 @@ 'use strict'; | ||
exports.replace_characters = replace_characters; | ||
exports.extract_possible_number = extract_possible_number; | ||
exports.is_viable_phone_number = is_viable_phone_number; | ||
@@ -28,2 +27,4 @@ exports.extract_formatted_phone_number = extract_formatted_phone_number; | ||
exports.is_national_phone_number = is_national_phone_number; | ||
exports.get_number_type = get_number_type; | ||
exports.is_of_type = is_of_type; | ||
@@ -103,5 +104,3 @@ var _common = require('./common'); | ||
// Regular expression of trailing characters that we want to remove. We remove | ||
// all characters that are not alpha or numerical characters. The hash character | ||
// is retained here, as it may signify the previous block was an extension. | ||
// Regular expression of trailing characters that we want to remove. | ||
var AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$'); | ||
@@ -210,3 +209,3 @@ | ||
// If the phone number is not viable, then abort. | ||
if (!formatted_phone_number) { | ||
if (!is_viable_phone_number(formatted_phone_number)) { | ||
return {}; | ||
@@ -266,2 +265,8 @@ } | ||
country = find_country_code(country_phone_code, national_number); | ||
// Just in case there's a bug in Google's metadata | ||
/* istanbul ignore if */ | ||
if (!country) { | ||
return {}; | ||
} | ||
} | ||
@@ -335,17 +340,2 @@ | ||
// Attempts to extract a possible number from the string passed in. | ||
function extract_possible_number(text) { | ||
var starts_at = text.search(PHONE_NUMBER_START_PATTERN); | ||
if (starts_at < 0) { | ||
return ''; | ||
} | ||
return text | ||
// Trim everything to the left of the phone number | ||
.slice(starts_at) | ||
// Remove trailing non-numerical characters | ||
.replace(AFTER_PHONE_NUMBER_END_PATTERN, ''); | ||
} | ||
// Checks to see if the string of characters could possibly be a phone number at | ||
@@ -363,23 +353,25 @@ // all. At the moment, checks to see that the string begins with at least 2 | ||
function extract_formatted_phone_number(text) { | ||
var is_valid = arguments.length <= 1 || arguments[1] === undefined ? is_viable_phone_number : arguments[1]; | ||
if (!text || text.length > MAX_INPUT_STRING_LENGTH) { | ||
return; | ||
return ''; | ||
} | ||
// Extracts a piece of text possibly containing a phone number | ||
text = extract_possible_number(text); | ||
// Attempt to extract a possible number from the string passed in | ||
if (is_valid(text)) { | ||
return text; | ||
var starts_at = text.search(PHONE_NUMBER_START_PATTERN); | ||
if (starts_at < 0) { | ||
return ''; | ||
} | ||
return text | ||
// Trim everything to the left of the phone number | ||
.slice(starts_at) | ||
// Remove trailing non-numerical characters | ||
.replace(AFTER_PHONE_NUMBER_END_PATTERN, ''); | ||
} | ||
// Parses a formatted phone number | ||
// and returns `{ is_international, number }` | ||
// where `number` is either national (significant) phone number | ||
// or an international phone number with the leading '+' stripped. | ||
// Parses a formatted phone number. | ||
function parse_phone_number(number) { | ||
if (!number) { | ||
return {}; | ||
return ''; | ||
} | ||
@@ -393,3 +385,7 @@ | ||
return { number: number, is_international: is_international }; | ||
if (is_international) { | ||
return '+' + number; | ||
} | ||
return number; | ||
} | ||
@@ -403,9 +399,5 @@ | ||
// | ||
function parse_phone_number_and_country_phone_code(_number) { | ||
var _parse_phone_number = parse_phone_number(_number); | ||
function parse_phone_number_and_country_phone_code(number) { | ||
number = parse_phone_number(number); | ||
var number = _parse_phone_number.number; | ||
var is_international = _parse_phone_number.is_international; | ||
if (!number) { | ||
@@ -417,6 +409,9 @@ return {}; | ||
// then don't extract country phone code. | ||
if (!is_international) { | ||
if (number[0] !== '+') { | ||
return { number: number }; | ||
} | ||
// Strip the leading '+' sign | ||
number = number.slice(1); | ||
// Fast abortion: country codes do not begin with a '0' | ||
@@ -492,42 +487,41 @@ if (number[0] === '0') { | ||
function find_country_code(country_phone_code, national_phone_number) { | ||
// Is always defined, because `country_phone_code` is always valid | ||
var possible_country_codes = _metadata2.default.country_phone_code_to_countries[country_phone_code]; | ||
// Is always non-empty, because `country_phone_code` is always valid | ||
var possible_countries = _metadata2.default.country_phone_code_to_countries[country_phone_code]; | ||
// Iterate possible countries backwards | ||
// because the first one is the default (main) one. | ||
var i = void 0; | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
// Look for leading digits for countries | ||
i = possible_country_codes.length - 1; | ||
while (i > 0) { | ||
var country_code = possible_country_codes[i]; | ||
try { | ||
for (var _iterator2 = (0, _getIterator3.default)(possible_countries), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var country_code = _step2.value; | ||
var country = _metadata2.default.countries[country_code]; | ||
var country = _metadata2.default.countries[country_code]; | ||
if ((0, _metadata3.get_leading_digits)(country)) { | ||
if (national_phone_number && national_phone_number.search((0, _metadata3.get_leading_digits)(country)) === 0) { | ||
return country_code; | ||
// Leading digits check would be the simplest one | ||
if ((0, _metadata3.get_leading_digits)(country)) { | ||
if (national_phone_number && national_phone_number.search((0, _metadata3.get_leading_digits)(country)) === 0) { | ||
return country_code; | ||
} | ||
} | ||
// Else perform full validation with all of those bulky | ||
// fixed-line/mobile/etc regular expressions. | ||
else if (get_number_type(national_phone_number, country)) { | ||
return country_code; | ||
} | ||
} | ||
i--; | ||
} | ||
// Leading digits not matched, | ||
// just phone number validation will do. | ||
// Now start from the default one. | ||
i = 0; | ||
while (i < possible_country_codes.length) { | ||
var _country_code = possible_country_codes[i]; | ||
var _country = _metadata2.default.countries[_country_code]; | ||
if (is_national_phone_number(national_phone_number, _country)) { | ||
return _country_code; | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
} | ||
} | ||
i++; | ||
} | ||
// No country matched this national phone number | ||
} | ||
@@ -546,4 +540,81 @@ | ||
// If leading digits are specified, then check them. | ||
if ((0, _metadata3.get_leading_digits)(country_metadata)) { | ||
if (national_number.indexOf((0, _metadata3.get_leading_digits)(country_metadata)) !== 0) { | ||
return false; | ||
} | ||
} | ||
return (0, _common.matches_entirely)((0, _metadata3.get_national_number_pattern)(country_metadata), national_number); | ||
} | ||
// Finds out national phone number type (fixed line, mobile, etc) | ||
function get_number_type(national_number, country_metadata) { | ||
// Is this national number even valid for this country | ||
if (!is_of_type(national_number, (0, _metadata3.get_national_number_pattern)(country_metadata))) { | ||
return; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_mobile)(country_metadata))) { | ||
if (is_of_type(national_number, (0, _metadata3.get_type_fixed_line)(country_metadata))) { | ||
return 'FIXED_LINE_OR_MOBILE'; | ||
} | ||
return 'MOBILE'; | ||
} | ||
// Is it fixed line number | ||
if (is_of_type(national_number, (0, _metadata3.get_type_fixed_line)(country_metadata))) { | ||
return 'FIXED_LINE'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_toll_free)(country_metadata))) { | ||
return 'TOLL_FREE'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_premium_rate)(country_metadata))) { | ||
return 'PREMIUM_RATE'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_personal_number)(country_metadata))) { | ||
return 'PERSONAL_NUMBER'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_voice_mail)(country_metadata))) { | ||
return 'VOICEMAIL'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_uan)(country_metadata))) { | ||
return 'UAN'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_pager)(country_metadata))) { | ||
return 'PAGER'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_voip)(country_metadata))) { | ||
return 'VOIP'; | ||
} | ||
if (is_of_type(national_number, (0, _metadata3.get_type_shared_cost)(country_metadata))) { | ||
return 'SHARED_COST'; | ||
} | ||
} | ||
function is_of_type(national_number, type) { | ||
// // Check if any possible number lengths are present; | ||
// // if so, we use them to avoid checking | ||
// // the validation pattern if they don't match. | ||
// // If they are absent, this means they match | ||
// // the general description, which we have | ||
// // already checked before a specific number type. | ||
// if (get_possible_lengths(type) && | ||
// get_possible_lengths(type).indexOf(national_number.length) === -1) | ||
// { | ||
// return false | ||
// } | ||
// get_type_pattern(type) === type | ||
return (0, _common.matches_entirely)(type, national_number); | ||
} | ||
//# sourceMappingURL=parse.js.map |
@@ -0,1 +1,6 @@ | ||
0.1.10 / 30.11.2016 | ||
=================== | ||
* Turned out those numerous bulky regular expressions (`<fixedLine/>`, `<mobile/>`, etc) are actually required to reliably infer country from country calling code and national phone number in cases where there are multiple countries assigned to the same country phone code (e.g. NANPA), so I've included those big regular expressions for those ambiguous cases which increased metadata size by 20 KiloBytes resulting in a total of 90 KiloBytes for the metadata. | ||
0.1.9 / 30.11.2016 | ||
@@ -2,0 +7,0 @@ =================== |
{ | ||
"name": "libphonenumber-js", | ||
"version": "0.1.9", | ||
"version": "0.1.10", | ||
"description": "A simpler (and smaller) rewrite of Google Android's famous libphonenumber library", | ||
@@ -5,0 +5,0 @@ "main": "index.common.js", |
@@ -15,3 +15,3 @@ # libphonenumber-js | ||
One part of me was curious about how all this phone matching machinery worked, and another part of me was curious if there's a way to reduce those 220 KiloBytes to something more reasonable while also getting rid of the `closure` library and rewriting it all in pure javascript. So, that was my little hackathon for a couple of weeks, and seems that it succeeded. The resulting library does everything a modern web application needs while maintaining a much smaller size of about 70 KiloBytes. | ||
One part of me was curious about how all this phone matching machinery worked, and another part of me was curious if there's a way to reduce those 220 KiloBytes to something more reasonable while also getting rid of the `closure` library and rewriting it all in pure javascript. So, that was my little hackathon for a couple of weeks, and seems that it succeeded. The resulting library does everything a modern web application needs while maintaining a much smaller size of about 90 KiloBytes. | ||
@@ -21,3 +21,3 @@ ## Difference from Google's `libphonenumber` | ||
* Pure javascript, doesn't require any 3rd party libraries | ||
* Metadata size is just about 70 KiloBytes while the original `libphonenumber` metadata size is about 200 KiloBytes | ||
* Metadata size is just about 90 KiloBytes while the original `libphonenumber` metadata size is about 200 KiloBytes | ||
* Better "as you type" formatting | ||
@@ -125,2 +125,6 @@ * Doesn't parse alphabetic phone numbers like `1-800-GOT-MILK` as we don't use telephone sets in the XXIst century that much (and we have phonebooks in your mobile phones) | ||
## Bug reporting | ||
If you spot any inconsistencies with the [original Google's `libphonenumber`](https://libphonenumber.appspot.com/) then create an issue in this repo. | ||
## Contributing | ||
@@ -127,0 +131,0 @@ |
import path from 'path' | ||
import fs from 'fs' | ||
import compress from '../source/compress' | ||
import compress from '../source/tools/compress' | ||
@@ -6,0 +6,0 @@ const input = fs.readFileSync(path.join(__dirname, '../metadata.json'), 'utf8') |
@@ -1,2 +0,2 @@ | ||
import generate from '../source/generate' | ||
import generate from '../source/tools/generate' | ||
@@ -3,0 +3,0 @@ import path from 'path' |
@@ -49,2 +49,12 @@ // This is an enhanced port of Google Android `libphonenumber`'s | ||
// Used in phone number format template creation. | ||
// Could be any digit, I guess. | ||
const DUMMY_DIGIT = '9' | ||
const DUMMY_DIGIT_MATCHER = new RegExp(DUMMY_DIGIT, 'g') | ||
// I don't know why is it exactly `15` | ||
const LONGEST_NATIONAL_PHONE_NUMBER_LENGTH = 15 | ||
// Create a phone number consisting only of the digit 9 that matches the | ||
// `number_pattern` by applying the pattern to the "longest phone number" string. | ||
const LONGEST_DUMMY_PHONE_NUMBER = repeat(DUMMY_DIGIT, LONGEST_NATIONAL_PHONE_NUMBER_LENGTH) | ||
// The digits that have not been entered yet will be represented by a \u2008, | ||
@@ -91,3 +101,3 @@ // the punctuation space. | ||
VALID_DIGITS + | ||
']+' | ||
']*' | ||
@@ -112,31 +122,24 @@ const VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i') | ||
{ | ||
// this.original_input += text | ||
// Parse input | ||
let extracted_number = extract_formatted_phone_number(text, number => matches_entirely(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, number)) | ||
let extracted_number = extract_formatted_phone_number(text) | ||
let { number, is_international } = parse_phone_number(extracted_number) | ||
// Special case for just the leading '+' | ||
if (!extracted_number && text.indexOf('+') >= 0) | ||
// Special case for a lone '+' sign | ||
// since it's not considered a possible phone number. | ||
if (!extracted_number) | ||
{ | ||
is_international = true | ||
if (text.indexOf('+') >= 0) | ||
{ | ||
extracted_number = '+' | ||
} | ||
} | ||
let parsed_input = '' | ||
if (is_international) | ||
// Validate possible first part of a phone number | ||
if (!matches_entirely(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, extracted_number)) | ||
{ | ||
parsed_input += '+' | ||
return this.current_output | ||
} | ||
if (number) | ||
{ | ||
parsed_input += number | ||
} | ||
// Feed the parsed input character-by-character | ||
for (let character of parsed_input) | ||
for (let character of parse_phone_number(extracted_number)) | ||
{ | ||
@@ -268,3 +271,3 @@ this.current_output = this.input_character(character) | ||
// then return the formatted number so far. | ||
if (this.current_format) | ||
if (national_number_formatted_with_previous_format) | ||
{ | ||
@@ -313,3 +316,3 @@ return this.full_phone_number(national_number_formatted_with_previous_format) | ||
this.current_format = undefined | ||
this.chosen_format = undefined | ||
@@ -319,2 +322,3 @@ this.last_match_position = 0 | ||
this.formatting_template = undefined | ||
this.partially_populated_formatting_template = undefined | ||
@@ -518,3 +522,3 @@ this.national_prefix_is_part_of_formatting_template = false | ||
// and is still possible, then stick to it. | ||
if (this.current_format === format) | ||
if (this.chosen_format === format) | ||
{ | ||
@@ -529,3 +533,3 @@ return | ||
{ | ||
this.current_format = format | ||
this.chosen_format = format | ||
@@ -576,16 +580,14 @@ // With a new formatting template, the matched position | ||
// Get a formatting template which can be used to efficiently format a | ||
// partial number where digits are added one by one. | ||
// Get a formatting template which can be used to efficiently format | ||
// a partial number where digits are added one by one. | ||
// Create a phone number consisting only of the digit 9 that matches the | ||
// `number_pattern` by applying the pattern to the "longest phone number" string. | ||
const longest_phone_number = '999999999999999' | ||
// This match will always succeed, | ||
// because the "longest dummy phone number" | ||
// has enough length to accomodate any possible | ||
// national phone number format pattern. | ||
const dummy_phone_number_matching_format_pattern = LONGEST_DUMMY_PHONE_NUMBER.match(number_pattern)[0] | ||
const matches = longest_phone_number.match(number_pattern) | ||
// This match will always succeed | ||
const phone_number = matches[0] | ||
// No formatting template can be created if the number of digits entered so | ||
// far is longer than the maximum the current formatting rule can accommodate. | ||
if (phone_number.length < this.national_number.length) | ||
// If the national number entered is too long | ||
// for any phone number format, then abort. | ||
if (this.national_number.length > dummy_phone_number_matching_format_pattern.length) | ||
{ | ||
@@ -595,7 +597,10 @@ return | ||
return this.formatting_template = phone_number | ||
// Formats the number according to numberFormat | ||
// Create formatting template for this phone number format | ||
this.formatting_template = dummy_phone_number_matching_format_pattern | ||
// Format the dummy phone number according to the format | ||
.replace(new RegExp(number_pattern, 'g'), number_format) | ||
// Replaces each digit with character DIGIT_PLACEHOLDER | ||
.replace(new RegExp('9', 'g'), DIGIT_PLACEHOLDER) | ||
// Replace each dummy digit with a DIGIT_PLACEHOLDER | ||
.replace(DUMMY_DIGIT_MATCHER, DIGIT_PLACEHOLDER) | ||
return this.partially_populated_formatting_template = this.formatting_template | ||
} | ||
@@ -608,10 +613,10 @@ | ||
// and return the formatted digits so far. | ||
if (this.formatting_template && this.formatting_template.slice(this.last_match_position + 1).search(DIGIT_PLACEHOLDER_MATCHER) >= 0) | ||
if (this.chosen_format && this.partially_populated_formatting_template.slice(this.last_match_position + 1).search(DIGIT_PLACEHOLDER_MATCHER) >= 0) | ||
{ | ||
const digit_pattern_start = this.formatting_template.search(DIGIT_PLACEHOLDER_MATCHER) | ||
this.formatting_template = this.formatting_template.replace(DIGIT_PLACEHOLDER_MATCHER, digit) | ||
const digit_pattern_start = this.partially_populated_formatting_template.search(DIGIT_PLACEHOLDER_MATCHER) | ||
this.partially_populated_formatting_template = this.partially_populated_formatting_template.replace(DIGIT_PLACEHOLDER_MATCHER, digit) | ||
this.last_match_position = digit_pattern_start | ||
// Return the formatted phone number so far | ||
return close_dangling_braces(this.formatting_template, digit_pattern_start + 1) | ||
return close_dangling_braces(this.partially_populated_formatting_template, digit_pattern_start + 1) | ||
.replace(DIGIT_PLACEHOLDER_MATCHER_GLOBAL, ' ') | ||
@@ -622,7 +627,9 @@ } | ||
// Reset the current format flag, | ||
// Reset the current format, | ||
// so that the new format will be chosen | ||
// in a subsequent `this.choose_another_format()` call | ||
// later in code. | ||
this.current_format = undefined | ||
this.chosen_format = undefined | ||
this.formatting_template = undefined | ||
this.partially_populated_formatting_template = undefined | ||
} | ||
@@ -681,2 +688,27 @@ | ||
return count | ||
} | ||
// Repeats a string (or a symbol) N times. | ||
// http://stackoverflow.com/questions/202605/repeat-string-javascript | ||
export function repeat(string, times) | ||
{ | ||
if (times < 1) | ||
{ | ||
return '' | ||
} | ||
let result = '' | ||
while (times > 1) | ||
{ | ||
if (times & 1) | ||
{ | ||
result += string | ||
} | ||
times >>= 1 | ||
string += string | ||
} | ||
return result + string | ||
} |
@@ -95,2 +95,62 @@ export function get_phone_code(country_metadata) | ||
return metadata.countries[country_code] | ||
} | ||
export function get_types(country_metadata) | ||
{ | ||
return country_metadata[9] | ||
} | ||
function get_type(country_metadata, index) | ||
{ | ||
return get_types(country_metadata) ? get_types(country_metadata)[index] : undefined | ||
} | ||
export function get_type_fixed_line(country_metadata) | ||
{ | ||
return get_type(country_metadata, 0) | ||
} | ||
export function get_type_mobile(country_metadata) | ||
{ | ||
return get_type(country_metadata, 1) | ||
} | ||
export function get_type_toll_free(country_metadata) | ||
{ | ||
return get_type(country_metadata, 2) | ||
} | ||
export function get_type_premium_rate(country_metadata) | ||
{ | ||
return get_type(country_metadata, 3) | ||
} | ||
export function get_type_personal_number(country_metadata) | ||
{ | ||
return get_type(country_metadata, 4) | ||
} | ||
export function get_type_voice_mail(country_metadata) | ||
{ | ||
return get_type(country_metadata, 5) | ||
} | ||
export function get_type_uan(country_metadata) | ||
{ | ||
return get_type(country_metadata, 6) | ||
} | ||
export function get_type_pager(country_metadata) | ||
{ | ||
return get_type(country_metadata, 7) | ||
} | ||
export function get_type_voip(country_metadata) | ||
{ | ||
return get_type(country_metadata, 8) | ||
} | ||
export function get_type_shared_cost(country_metadata) | ||
{ | ||
return get_type(country_metadata, 9) | ||
} |
@@ -16,3 +16,13 @@ // This is a port of Google Android `libphonenumber`'s | ||
get_leading_digits, | ||
get_metadata_by_country_phone_code | ||
get_metadata_by_country_phone_code, | ||
get_type_fixed_line, | ||
get_type_mobile, | ||
get_type_toll_free, | ||
get_type_premium_rate, | ||
get_type_personal_number, | ||
get_type_voice_mail, | ||
get_type_uan, | ||
get_type_pager, | ||
get_type_voip, | ||
get_type_shared_cost | ||
} | ||
@@ -97,5 +107,3 @@ from './metadata' | ||
// Regular expression of trailing characters that we want to remove. We remove | ||
// all characters that are not alpha or numerical characters. The hash character | ||
// is retained here, as it may signify the previous block was an extension. | ||
// Regular expression of trailing characters that we want to remove. | ||
const AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$') | ||
@@ -212,3 +220,3 @@ | ||
// If the phone number is not viable, then abort. | ||
if (!formatted_phone_number) | ||
if (!is_viable_phone_number(formatted_phone_number)) | ||
{ | ||
@@ -275,2 +283,9 @@ return {} | ||
country = find_country_code(country_phone_code, national_number) | ||
// Just in case there's a bug in Google's metadata | ||
/* istanbul ignore if */ | ||
if (!country) | ||
{ | ||
return {} | ||
} | ||
} | ||
@@ -329,19 +344,2 @@ | ||
// Attempts to extract a possible number from the string passed in. | ||
export function extract_possible_number(text) | ||
{ | ||
const starts_at = text.search(PHONE_NUMBER_START_PATTERN) | ||
if (starts_at < 0) | ||
{ | ||
return '' | ||
} | ||
return text | ||
// Trim everything to the left of the phone number | ||
.slice(starts_at) | ||
// Remove trailing non-numerical characters | ||
.replace(AFTER_PHONE_NUMBER_END_PATTERN, '') | ||
} | ||
// Checks to see if the string of characters could possibly be a phone number at | ||
@@ -360,22 +358,26 @@ // all. At the moment, checks to see that the string begins with at least 2 | ||
export function extract_formatted_phone_number(text, is_valid = is_viable_phone_number) | ||
export function extract_formatted_phone_number(text) | ||
{ | ||
if (!text || text.length > MAX_INPUT_STRING_LENGTH) | ||
{ | ||
return | ||
return '' | ||
} | ||
// Extracts a piece of text possibly containing a phone number | ||
text = extract_possible_number(text) | ||
// Attempt to extract a possible number from the string passed in | ||
if (is_valid(text)) | ||
const starts_at = text.search(PHONE_NUMBER_START_PATTERN) | ||
if (starts_at < 0) | ||
{ | ||
return text | ||
return '' | ||
} | ||
return text | ||
// Trim everything to the left of the phone number | ||
.slice(starts_at) | ||
// Remove trailing non-numerical characters | ||
.replace(AFTER_PHONE_NUMBER_END_PATTERN, '') | ||
} | ||
// Parses a formatted phone number | ||
// and returns `{ is_international, number }` | ||
// where `number` is either national (significant) phone number | ||
// or an international phone number with the leading '+' stripped. | ||
// Parses a formatted phone number. | ||
export function parse_phone_number(number) | ||
@@ -385,3 +387,3 @@ { | ||
{ | ||
return {} | ||
return '' | ||
} | ||
@@ -395,3 +397,8 @@ | ||
return { number, is_international } | ||
if (is_international) | ||
{ | ||
return `+${number}` | ||
} | ||
return number | ||
} | ||
@@ -405,5 +412,5 @@ | ||
// | ||
export function parse_phone_number_and_country_phone_code(_number) | ||
export function parse_phone_number_and_country_phone_code(number) | ||
{ | ||
const { number, is_international } = parse_phone_number(_number) | ||
number = parse_phone_number(number) | ||
@@ -417,3 +424,3 @@ if (!number) | ||
// then don't extract country phone code. | ||
if (!is_international) | ||
if (number[0] !== '+') | ||
{ | ||
@@ -423,2 +430,5 @@ return { number } | ||
// Strip the leading '+' sign | ||
number = number.slice(1) | ||
// Fast abortion: country codes do not begin with a '0' | ||
@@ -506,17 +516,10 @@ if (number[0] === '0') | ||
{ | ||
// Is always defined, because `country_phone_code` is always valid | ||
const possible_country_codes = metadata.country_phone_code_to_countries[country_phone_code] | ||
// Is always non-empty, because `country_phone_code` is always valid | ||
const possible_countries = metadata.country_phone_code_to_countries[country_phone_code] | ||
// Iterate possible countries backwards | ||
// because the first one is the default (main) one. | ||
let i | ||
// Look for leading digits for countries | ||
i = possible_country_codes.length - 1 | ||
while (i > 0) | ||
for (let country_code of possible_countries) | ||
{ | ||
const country_code = possible_country_codes[i] | ||
const country = metadata.countries[country_code] | ||
// Leading digits check would be the simplest one | ||
if (get_leading_digits(country)) | ||
@@ -530,25 +533,9 @@ { | ||
} | ||
i-- | ||
} | ||
// Leading digits not matched, | ||
// just phone number validation will do. | ||
// Now start from the default one. | ||
i = 0 | ||
while (i < possible_country_codes.length) | ||
{ | ||
const country_code = possible_country_codes[i] | ||
const country = metadata.countries[country_code] | ||
if (is_national_phone_number(national_phone_number, country)) | ||
// Else perform full validation with all of those bulky | ||
// fixed-line/mobile/etc regular expressions. | ||
else if (get_number_type(national_phone_number, country)) | ||
{ | ||
return country_code | ||
} | ||
i++ | ||
} | ||
// No country matched this national phone number | ||
} | ||
@@ -568,3 +555,96 @@ | ||
// If leading digits are specified, then check them. | ||
if (get_leading_digits(country_metadata)) | ||
{ | ||
if (national_number.indexOf(get_leading_digits(country_metadata)) !== 0) | ||
{ | ||
return false | ||
} | ||
} | ||
return matches_entirely(get_national_number_pattern(country_metadata), national_number) | ||
} | ||
// Finds out national phone number type (fixed line, mobile, etc) | ||
export function get_number_type(national_number, country_metadata) | ||
{ | ||
// Is this national number even valid for this country | ||
if (!is_of_type(national_number, get_national_number_pattern(country_metadata))) | ||
{ | ||
return | ||
} | ||
if (is_of_type(national_number, get_type_mobile(country_metadata))) | ||
{ | ||
if (is_of_type(national_number, get_type_fixed_line(country_metadata))) | ||
{ | ||
return 'FIXED_LINE_OR_MOBILE' | ||
} | ||
return 'MOBILE' | ||
} | ||
// Is it fixed line number | ||
if (is_of_type(national_number, get_type_fixed_line(country_metadata))) | ||
{ | ||
return 'FIXED_LINE' | ||
} | ||
if (is_of_type(national_number, get_type_toll_free(country_metadata))) | ||
{ | ||
return 'TOLL_FREE' | ||
} | ||
if (is_of_type(national_number, get_type_premium_rate(country_metadata))) | ||
{ | ||
return 'PREMIUM_RATE' | ||
} | ||
if (is_of_type(national_number, get_type_personal_number(country_metadata))) | ||
{ | ||
return 'PERSONAL_NUMBER' | ||
} | ||
if (is_of_type(national_number, get_type_voice_mail(country_metadata))) | ||
{ | ||
return 'VOICEMAIL' | ||
} | ||
if (is_of_type(national_number, get_type_uan(country_metadata))) | ||
{ | ||
return 'UAN' | ||
} | ||
if (is_of_type(national_number, get_type_pager(country_metadata))) | ||
{ | ||
return 'PAGER' | ||
} | ||
if (is_of_type(national_number, get_type_voip(country_metadata))) | ||
{ | ||
return 'VOIP' | ||
} | ||
if (is_of_type(national_number, get_type_shared_cost(country_metadata))) | ||
{ | ||
return 'SHARED_COST' | ||
} | ||
} | ||
export function is_of_type(national_number, type) | ||
{ | ||
// // Check if any possible number lengths are present; | ||
// // if so, we use them to avoid checking | ||
// // the validation pattern if they don't match. | ||
// // If they are absent, this means they match | ||
// // the general description, which we have | ||
// // already checked before a specific number type. | ||
// if (get_possible_lengths(type) && | ||
// get_possible_lengths(type).indexOf(national_number.length) === -1) | ||
// { | ||
// return false | ||
// } | ||
// get_type_pattern(type) === type | ||
return matches_entirely(type, national_number) | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1230873
3698
172