libphonenumber-js
Advanced tools
Comparing version 0.1.4 to 0.1.5
@@ -19,2 +19,5 @@ 'use strict'; | ||
exports.close_dangling_braces = close_dangling_braces; | ||
exports.count_occurences = count_occurences; | ||
var _metadata = require('../metadata.min'); | ||
@@ -28,2 +31,4 @@ | ||
var _format = require('./format'); | ||
var _common = require('./common'); | ||
@@ -35,3 +40,3 @@ | ||
// the punctuation space. | ||
// This is a port of Google Android `libphonenumber`'s | ||
var DIGIT_PLACEHOLDER = ' '; // This is an enhanced port of Google Android `libphonenumber`'s | ||
// `asyoutypeformatter.js` of 17th November, 2016. | ||
@@ -41,7 +46,4 @@ // | ||
var DIGIT_PLACEHOLDER = ' '; | ||
var DIGIT_PATTERN = new RegExp(DIGIT_PLACEHOLDER); | ||
var DIGIT_PLACEHOLDER_MATCHER = new RegExp(DIGIT_PLACEHOLDER); | ||
var SEPARATOR_BEFORE_NATIONAL_NUMBER = ' '; | ||
// A pattern that is used to match character classes in regular expressions. | ||
@@ -66,18 +68,7 @@ // An example of a character class is [1-4]. | ||
// A set of characters that, if found in a national prefix formatting rules, are | ||
// an indicator to us that we should separate the national prefix from the | ||
// number when formatting. | ||
var NATIONAL_PREFIX_SEPARATORS_PATTERN = /[- ]/; | ||
// This is the minimum length of national number accrued that is required to | ||
// trigger the formatter. The first element of the leadingDigitsPattern of | ||
// each numberFormat contains a regular expression that matches up to this | ||
// number of digits. | ||
// This is the minimum length of the leading digits of a phone number | ||
// to guarantee the first "leading digits pattern" for a phone number format | ||
// to be preemptive. | ||
var MIN_LEADING_DIGITS_LENGTH = 3; | ||
// A pattern that is used to determine if the national prefix formatting rule | ||
// has the first group only, i.e., does not start with the national prefix. | ||
// Note that the pattern explicitly allows for unbalanced parentheses. | ||
var FIRST_GROUP_ONLY_PREFIX_PATTERN = /^\(?\$1\)?$/; | ||
var VALID_INCOMPLETE_PHONE_NUMBER = '[' + _parse.PLUS_CHARS + ']{0,1}' + '[' + _parse.VALID_PUNCTUATION + _parse.VALID_DIGITS + ']+'; | ||
@@ -94,2 +85,3 @@ | ||
this.country_metadata = _metadata2.default.countries[country_code]; | ||
this.initialize_possible_formats(); | ||
} | ||
@@ -103,3 +95,3 @@ | ||
value: function input(text) { | ||
this.original_input += text; | ||
// this.original_input += text | ||
@@ -167,87 +159,110 @@ // Parse input | ||
// If an out of position '+' sign detected | ||
// (or a second '+' sign) | ||
// (or a second '+' sign), | ||
// then just don't allow it being input. | ||
if (this.parsed_input) { | ||
this.able_to_format = false; | ||
} else { | ||
this.parsed_input += character; | ||
this.prefix_before_national_number = '+'; | ||
return this.current_output; | ||
} | ||
} else { | ||
this.parsed_input += character; | ||
this.national_number += character; | ||
} | ||
// A digit then | ||
else { | ||
this.national_number += character; | ||
} | ||
this.parsed_input += character; | ||
// Try to format the parsed input | ||
if (!this.able_to_format) { | ||
// When we are unable to format because of reasons other than that | ||
// formatting chars have been entered, it can be due to really long IDDs or | ||
// NDDs. If that is the case, we might be able to do formatting again after | ||
// extracting them. | ||
if (this.is_international()) { | ||
if (this.is_international()) { | ||
if (!this.country_phone_code) { | ||
// If one looks at country phone codes | ||
// then he can notice that no one country phone code | ||
// is ever a (leftmost) substring of another country phone code. | ||
// So if a valid country code is extracted so far | ||
// then it means that this is the country code. | ||
if (this.extract_country_phone_code()) { | ||
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted(); | ||
// If the possible phone number formats | ||
// haven't been initialized during instance creation, | ||
// then do it. | ||
if (!this.country_code) { | ||
this.initialize_possible_formats(); | ||
} | ||
return '+' + this.country_phone_code; | ||
} | ||
} else if (this.extract_longer_national_prefix()) { | ||
// Add an additional space to separate long NDD and national significant | ||
// number for readability. We don't set shouldAddSpaceAfterNationalPrefix_ | ||
// to true, since we don't want this to change later when we choose | ||
// formatting templates. | ||
this.prefix_before_national_number += SEPARATOR_BEFORE_NATIONAL_NUMBER; | ||
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted(); | ||
// Return raw phone number | ||
return this.parsed_input; | ||
} | ||
return this.parsed_input; | ||
} else { | ||
if (!this.national_prefix) { | ||
// Possibly extract a national prefix | ||
this.extract_national_prefix(); | ||
} else if (!this.able_to_format) { | ||
if (!this.extract_longer_national_prefix()) { | ||
// Return raw phone number | ||
return this.parsed_input; | ||
} | ||
} | ||
} | ||
// We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH | ||
// digits (the plus sign is counted as a digit as well for this purpose) have | ||
// been entered. | ||
// Format the next phone number digit | ||
// since the previously chose phone number format | ||
// still holds. | ||
// | ||
// This is done here because if `attempt_to_format_complete_phone_number` | ||
// was placed before this call then the `formatting_template` | ||
// wouldn't reflect the situation correctly (and would therefore be inconsistent) | ||
// | ||
var national_number_formatted_with_previous_format = this.format_next_national_number_digit(character); | ||
if (this.parsed_input.length < MIN_LEADING_DIGITS_LENGTH) { | ||
return this.parsed_input; | ||
} | ||
// See if the input digits can be formatted properly already. If not, | ||
// use the results from format_next_national_number_digit(), which does formatting | ||
// based on the formatting pattern chosen. | ||
if (this.parsed_input.length === MIN_LEADING_DIGITS_LENGTH) { | ||
if (this.is_international()) { | ||
this.expecting_country_calling_code = true; | ||
} else { | ||
// No IDD or plus sign is found, might be entering in national format. | ||
this.national_prefix = this.extract_national_prefix(); | ||
return this.attempt_to_choose_formatting_pattern(); | ||
} | ||
} | ||
var formatted_number = this.attempt_to_format_complete_phone_number(); | ||
if (this.expecting_country_calling_code) { | ||
if (this.extract_country_phone_code()) { | ||
this.expecting_country_calling_code = false; | ||
} | ||
return this.prefix_before_national_number + this.national_number; | ||
if (formatted_number) { | ||
return formatted_number; | ||
} | ||
if (this.possible_formats.length === 0) { | ||
return this.attempt_to_choose_formatting_pattern(); | ||
} | ||
this.filter_possible_formats_by_leading_digits(); | ||
// The formatting patterns are already chosen. | ||
// If the previously chosen phone number format | ||
// didn't match the next digit being input | ||
// (leading digits). | ||
if (this.choose_another_format()) { | ||
// And a more appropriate phone number format | ||
// has been chosen for these `leading digits`, | ||
// then format the national phone number (so far) | ||
// using the newly selected phone number pattern. | ||
var national_number = this.input_national_number_digit(character); | ||
var formatted_national_number = this.reformat_national_number(); | ||
// See if the accrued digits can be formatted properly already. If not, | ||
// use the results from input_national_number_digit(), which does formatting | ||
// based on the formatting pattern chosen. | ||
var formatted_number = this.attempt_to_format_complete_phone_number(); | ||
if (formatted_national_number) { | ||
return this.full_phone_number(formatted_national_number); | ||
} | ||
if (formatted_number) { | ||
return formatted_number; | ||
// Couldn't format the supplied national number | ||
// using the selected phone number pattern. | ||
// Return raw phone number. | ||
return this.parsed_input; | ||
} | ||
this.narrow_down_possible_formats(this.national_number); | ||
// If no new phone number format could be chosen, | ||
// then can't format the phone. | ||
if (!this.current_format) { | ||
this.able_to_format = false; | ||
if (this.refresh_format()) { | ||
return this.retype_national_number(); | ||
// Return raw phone number | ||
return this.parsed_input; | ||
} | ||
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input; | ||
if (national_number_formatted_with_previous_format) { | ||
return this.full_phone_number(national_number_formatted_with_previous_format); | ||
} | ||
// Couldn't format the supplied national number | ||
// using the selected phone number pattern. | ||
// Return raw phone number | ||
return this.parsed_input; | ||
} | ||
@@ -257,4 +272,4 @@ }, { | ||
value: function clear() { | ||
// Input text so far, can contain any characters | ||
this.original_input = ''; | ||
// // Input text so far, can contain any characters | ||
// this.original_input = '' | ||
@@ -267,9 +282,2 @@ // Input stripped of non-phone-number characters. | ||
this.expecting_country_calling_code = false; | ||
// This contains anything that has been entered so far preceding the national | ||
// significant number, and it is formatted (e.g. with space inserted). For | ||
// example, this can contain IDD, country code, and/or NDD, etc. | ||
this.prefix_before_national_number = ''; | ||
// This contains the national prefix that has been extracted. It contains only | ||
@@ -279,6 +287,10 @@ // digits without formatting. | ||
this.should_add_space_after_national_prefix = false; | ||
this.national_number = ''; | ||
this.country_phone_code = ''; | ||
if (!this.country_code) { | ||
this.country_metadata = undefined; | ||
} | ||
this.clear_formatting(); | ||
@@ -289,7 +301,8 @@ } | ||
value: function clear_formatting() { | ||
// This indicates whether AsYouTypeFormatter is currently doing the formatting. | ||
this.able_to_format = true; | ||
this.possible_formats = []; | ||
this.possible_formats = undefined; | ||
this.current_format = undefined; | ||
this.last_match_position = 0; | ||
@@ -299,13 +312,14 @@ | ||
// The pattern from numberFormat that is currently used to create formattingTemplate. | ||
this.current_formatting_pattern = undefined; | ||
this.national_prefix_is_part_of_formatting_template = false; | ||
} | ||
// Format each digit of national phone number (so far) | ||
// using the newly selected phone number pattern. | ||
}, { | ||
key: 'retype_national_number', | ||
value: function retype_national_number() { | ||
if (!this.national_number) { | ||
return this.prefix_before_national_number; | ||
} | ||
var national_number = void 0; | ||
key: 'reformat_national_number', | ||
value: function reformat_national_number() { | ||
// Format each digit of national phone number (so far) | ||
// using the selected phone number pattern. | ||
var formatted_national_number = void 0; | ||
var _iteratorNormalCompletion2 = true; | ||
@@ -319,3 +333,3 @@ var _didIteratorError2 = false; | ||
national_number = this.input_national_number_digit(character); | ||
formatted_national_number = this.format_next_national_number_digit(character); | ||
} | ||
@@ -337,3 +351,3 @@ } catch (err) { | ||
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input; | ||
return formatted_national_number; | ||
} | ||
@@ -345,33 +359,7 @@ }, { | ||
this.expecting_country_calling_code = false; | ||
return this.attempt_to_choose_formatting_pattern(); | ||
return this.format_national_number(); | ||
} | ||
}, { | ||
key: 'attempt_to_choose_formatting_pattern', | ||
value: function attempt_to_choose_formatting_pattern() { | ||
// We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH | ||
// digits of national number (excluding national prefix) have been entered. | ||
if (this.national_number.length < MIN_LEADING_DIGITS_LENGTH) { | ||
return this.full_phone_number(this.national_number); | ||
} | ||
this.refresh_possible_formats(this.national_number); | ||
// See if the accrued digits can be formatted properly already. | ||
var formatted_number = this.attempt_to_format_complete_phone_number(); | ||
if (formatted_number) { | ||
return formatted_number; | ||
} | ||
if (this.refresh_format()) { | ||
return this.retype_national_number(); | ||
} | ||
return this.parsed_input; | ||
} | ||
}, { | ||
key: 'refresh_possible_formats', | ||
value: function refresh_possible_formats(leading_digits) { | ||
key: 'initialize_possible_formats', | ||
value: function initialize_possible_formats() { | ||
if (!this.country_metadata) { | ||
@@ -381,16 +369,30 @@ return; | ||
var national_prefix = (0, _metadata3.get_national_prefix)(this.country_metadata); | ||
this.possible_formats = (0, _metadata3.get_formats)(this.country_metadata).filter(function (format) { | ||
// Get all "eligible" phone number formats for this country | ||
this.available_formats = (0, _metadata3.get_formats)(this.country_metadata).filter(function (format) { | ||
return ELIGIBLE_FORMAT_PATTERN.test((0, _metadata3.get_format_international_format)(format)); | ||
}); | ||
this.narrow_down_possible_formats(leading_digits); | ||
this.possible_formats = this.available_formats; | ||
} | ||
}, { | ||
key: 'narrow_down_possible_formats', | ||
value: function narrow_down_possible_formats(leading_digits) { | ||
key: 'filter_possible_formats_by_leading_digits', | ||
value: function filter_possible_formats_by_leading_digits() { | ||
var leading_digits = this.national_number; | ||
// "leading digits" patterns start with a maximum 3 digits, | ||
// and then with each additional digit | ||
// a more precise "leading digits" pattern is specified. | ||
// They could make "leading digits" patterns start | ||
// with a maximum of a single digit, but they didn't, | ||
// so it's possible that some phone number formats | ||
// will be falsely rejected until there are at least | ||
// 3 digits in the national (significant) number being input. | ||
var index_of_leading_digits_pattern = leading_digits.length - MIN_LEADING_DIGITS_LENGTH; | ||
this.possible_formats = this.possible_formats.filter(function (format) { | ||
if (index_of_leading_digits_pattern < 0) { | ||
index_of_leading_digits_pattern = 0; | ||
} | ||
this.possible_formats = this.get_possible_formats().filter(function (format) { | ||
var leading_digits_pattern_count = (0, _metadata3.get_format_leading_digits_patterns)(format).length; | ||
@@ -403,8 +405,19 @@ | ||
var suitable_leading_digits_pattern_index = Math.min(index_of_leading_digits_pattern, leading_digits_pattern_count - 1); | ||
var leading_digits_pattern = (0, _metadata3.get_format_leading_digits_patterns)(format)[suitable_leading_digits_pattern_index]; | ||
return leading_digits.search(leading_digits_pattern) === 0; | ||
var leading_digits_pattern_index = Math.min(index_of_leading_digits_pattern, leading_digits_pattern_count - 1); | ||
var leading_digits_pattern = (0, _metadata3.get_format_leading_digits_patterns)(format)[leading_digits_pattern_index]; | ||
return new RegExp('^' + leading_digits_pattern).test(leading_digits); | ||
}); | ||
} | ||
}, { | ||
key: 'get_possible_formats', | ||
value: function get_possible_formats() { | ||
var leading_digits = this.national_number; | ||
if (leading_digits.length <= MIN_LEADING_DIGITS_LENGTH) { | ||
return this.available_formats; | ||
} | ||
return this.possible_formats; | ||
} | ||
// Check to see if there is an exact pattern match for these digits. If so, we | ||
@@ -422,13 +435,10 @@ // should use this instead of any other formatting template whose | ||
try { | ||
for (var _iterator3 = (0, _getIterator3.default)(this.possible_formats), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
for (var _iterator3 = (0, _getIterator3.default)(this.get_possible_formats()), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
var format = _step3.value; | ||
var pattern = (0, _metadata3.get_format_pattern)(format); | ||
var pattern_matcher = new RegExp('^(?:' + pattern + ')$'); | ||
var matcher = new RegExp('^(?:' + (0, _metadata3.get_format_pattern)(format) + ')$'); | ||
if (pattern_matcher.test(this.national_number)) { | ||
this.should_add_space_after_national_prefix = NATIONAL_PREFIX_SEPARATORS_PATTERN.test((0, _metadata3.get_format_national_prefix_formatting_rule)(format, this.country_metadata)); | ||
if (matcher.test(this.national_number)) { | ||
var formatted_national_number = (0, _format.format_national_number_using_format)(this.national_number, format, this.is_international(), this.national_prefix, this.country_metadata); | ||
var formatted_national_number = this.national_number.replace(new RegExp(pattern, 'g'), this.get_format_format(format)); | ||
return this.full_phone_number(formatted_national_number); | ||
@@ -453,5 +463,3 @@ } | ||
// Combines the national number with any prefix (IDD/+ and country code or | ||
// national prefix) that was collected. A space will be inserted between them if | ||
// the current formatting template indicates this to be suitable. | ||
// Combines the national number with the appropriate prefix | ||
@@ -461,16 +469,12 @@ }, { | ||
value: function full_phone_number(formatted_national_number) { | ||
if (this.should_add_space_after_national_prefix && this.prefix_before_national_number && this.prefix_before_national_number[this.prefix_before_national_number.length - 1] !== SEPARATOR_BEFORE_NATIONAL_NUMBER) { | ||
// We want to add a space after the national prefix if the national prefix | ||
// formatting rule indicates that this would normally be done, with the | ||
// exception of the case where we already appended a space because the NDD | ||
// was surprisingly long. | ||
return this.prefix_before_national_number + SEPARATOR_BEFORE_NATIONAL_NUMBER + formatted_national_number; | ||
if (this.is_international()) { | ||
return '+' + this.country_phone_code + ' ' + formatted_national_number; | ||
} | ||
return this.prefix_before_national_number + formatted_national_number; | ||
return formatted_national_number; | ||
} | ||
// Extracts the country calling code from the beginning of nationalNumber to | ||
// prefixBeforeNationalNumber when they are available, and places the remaining | ||
// input into nationalNumber. | ||
// Extracts the country calling code from the beginning | ||
// of the entered `national_number` (so far), | ||
// and places the remaining input into the `national_number`. | ||
@@ -503,11 +507,5 @@ }, { | ||
this.country_phone_code = country_phone_code; | ||
this.national_number = number; | ||
this.prefix_before_national_number += country_phone_code + SEPARATOR_BEFORE_NATIONAL_NUMBER; | ||
// When we have successfully extracted the IDD, | ||
// the previously extracted national prefix | ||
// should be cleared because it is no longer valid. | ||
this.national_prefix = ''; | ||
return this.country_metadata = (0, _metadata3.get_metadata_by_country_phone_code)(country_phone_code, _metadata2.default); | ||
@@ -523,19 +521,14 @@ } | ||
value: function extract_longer_national_prefix() { | ||
if (this.national_prefix) { | ||
// Put the extracted national prefix back to the national number | ||
// before attempting to extract a new national prefix. | ||
this.national_number = this.national_prefix + this.national_number; | ||
// Remove the previously extracted national prefix from prefixBeforeNationalNumber. We | ||
// cannot simply set it to empty string because people sometimes incorrectly | ||
// enter national prefix after the country code, e.g. +44 (0)20-1234-5678. | ||
var index_of_previous_national_prefix = this.prefix_before_national_number.lastIndexOf(this.national_prefix); | ||
this.prefix_before_national_number = this.prefix_before_national_number.slice(0, index_of_previous_national_prefix); | ||
if (!this.national_prefix) { | ||
return; | ||
} | ||
return this.national_prefix !== this.extract_national_prefix(); | ||
// Put the extracted national prefix back to the national number | ||
// before attempting to extract a new national prefix. | ||
this.national_number = this.national_prefix + this.national_number; | ||
var previously_extracted_national_prefix = this.national_prefix; | ||
this.extract_national_prefix(); | ||
return this.national_prefix !== previously_extracted_national_prefix; | ||
} | ||
// Returns the national prefix extracted, or an empty string if it is not present. | ||
}, { | ||
@@ -547,5 +540,7 @@ key: 'extract_national_prefix', | ||
if (this.country_metadata) { | ||
// Small performance optimization for NANPA countries | ||
// which can't have `1` (national prefix) as the | ||
// first digit of a national (significant) number | ||
if (this.is_NANPA_number_with_international_prefix()) { | ||
national_number_starts_at = 1; | ||
this.prefix_before_national_number += '1' + SEPARATOR_BEFORE_NATIONAL_NUMBER; | ||
} else if ((0, _metadata3.get_national_prefix_for_parsing)(this.country_metadata)) { | ||
@@ -558,3 +553,2 @@ var national_prefix_for_parsing = new RegExp('^(?:' + (0, _metadata3.get_national_prefix_for_parsing)(this.country_metadata) + ')'); | ||
national_number_starts_at = matches[0].length; | ||
this.prefix_before_national_number += this.national_number.substring(0, national_number_starts_at); | ||
} | ||
@@ -564,4 +558,5 @@ } | ||
this.national_prefix = this.national_number.slice(0, national_number_starts_at); | ||
this.national_number = this.national_number.slice(national_number_starts_at); | ||
return this.national_number.slice(0, national_number_starts_at); | ||
return this.national_prefix; | ||
} | ||
@@ -586,4 +581,4 @@ | ||
}, { | ||
key: 'refresh_format', | ||
value: function refresh_format() { | ||
key: 'choose_another_format', | ||
value: function choose_another_format() { | ||
// When there are multiple available formats, the formatter uses the first | ||
@@ -596,18 +591,19 @@ // format where a formatting template could be created. | ||
try { | ||
for (var _iterator4 = (0, _getIterator3.default)(this.possible_formats), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
for (var _iterator4 = (0, _getIterator3.default)(this.get_possible_formats()), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
var format = _step4.value; | ||
var pattern = (0, _metadata3.get_format_pattern)(format); | ||
if (this.current_formatting_pattern === pattern) { | ||
return false; | ||
// If this format is currently being used | ||
// and is still possible, then stick to it. | ||
if (this.current_format === format) { | ||
return; | ||
} | ||
// If this `format` is suitable for "as you type", | ||
// then extract the template from this format | ||
// and use it to format the phone number being input. | ||
if (this.create_formatting_template(format)) { | ||
this.current_formatting_pattern = pattern; | ||
this.current_format = format; | ||
this.should_add_space_after_national_prefix = NATIONAL_PREFIX_SEPARATORS_PATTERN.test((0, _metadata3.get_format_national_prefix_formatting_rule)(format, this.country_metadata)); | ||
// With a new formatting template, the matched position using the old | ||
// template needs to be reset. | ||
// With a new formatting template, the matched position | ||
// using the old template needs to be reset. | ||
this.last_match_position = 0; | ||
@@ -632,4 +628,2 @@ | ||
} | ||
this.able_to_format = false; | ||
} | ||
@@ -650,16 +644,19 @@ }, { | ||
.replace(CHARACTER_CLASS_PATTERN, '\\d') | ||
// Replace any standalone digit (not the one in d{}) with \d | ||
// Replace any standalone digit (not the one in `{}`) with \d | ||
.replace(STANDALONE_DIGIT_PATTERN, '\\d'); | ||
return this.formatting_template = this.get_formatting_template(number_pattern, this.get_format_format(format)); | ||
} | ||
var number_format = this.get_format_format(format); | ||
this.national_prefix_is_part_of_formatting_template = false; | ||
// Gets a formatting template which can be used to efficiently format a | ||
// partial number where digits are added one by one. | ||
if (this.national_prefix) { | ||
this.national_prefix_is_part_of_formatting_template = true; | ||
var national_prefix_formatting_rule = (0, _metadata3.get_format_national_prefix_formatting_rule)(format, this.country_metadata); | ||
number_format = number_format.replace(_format.FIRST_GROUP_PATTERN, national_prefix_formatting_rule); | ||
} | ||
}, { | ||
key: 'get_formatting_template', | ||
value: function get_formatting_template(number_pattern, number_format) { | ||
// Creates a phone number consisting only of the digit 9 that matches the | ||
// numberPattern by applying the pattern to the longestPhoneNumber string. | ||
// 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'; | ||
@@ -677,3 +674,3 @@ | ||
return phone_number | ||
return this.formatting_template = phone_number | ||
// Formats the number according to numberFormat | ||
@@ -685,21 +682,23 @@ .replace(new RegExp(number_pattern, 'g'), number_format) | ||
}, { | ||
key: 'input_national_number_digit', | ||
value: function input_national_number_digit(digit) { | ||
if (this.formatting_template && this.formatting_template.slice(this.last_match_position).search(DIGIT_PATTERN) >= 0) { | ||
var digit_pattern_start = this.formatting_template.search(DIGIT_PATTERN); | ||
this.formatting_template = this.formatting_template.replace(DIGIT_PATTERN, digit); | ||
key: 'format_next_national_number_digit', | ||
value: function format_next_national_number_digit(digit) { | ||
// If there is room for more digits in current `formatting_template`, | ||
// then set the next digit in the `formatting_template`, | ||
// 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); | ||
this.last_match_position = digit_pattern_start; | ||
return this.formatting_template.slice(0, digit_pattern_start + 1); | ||
// Return the formatted phone number so far | ||
return close_dangling_braces(this.formatting_template, digit_pattern_start + 1); | ||
} | ||
if (this.possible_formats.length === 1) { | ||
// More digits are entered than we could handle, and there are | ||
// no other valid patterns to try. | ||
this.able_to_format = false; | ||
} | ||
// else, we just reset the formatting pattern | ||
// More digits are entered than the current format could handle | ||
this.current_formatting_pattern = undefined; | ||
return this.parsed_input; | ||
// Reset the current format flag, | ||
// so that the new format will be chosen | ||
// in a subsequent `this.choose_another_format()` call | ||
// later in code. | ||
this.current_format = undefined; | ||
} | ||
@@ -714,9 +713,4 @@ }, { | ||
value: function get_format_format(format) { | ||
// // Always prefer international formatting rules over national ones, | ||
// // because national formatting rules could contain | ||
// // local formatting rules for numbers entered without area code. | ||
// get_format_international_format(format) | ||
if (this.is_international()) { | ||
return (0, _metadata3.get_format_international_format)(format); | ||
return (0, _format.local_to_international_style)((0, _metadata3.get_format_international_format)(format)); | ||
} | ||
@@ -731,3 +725,53 @@ | ||
exports.default = as_you_type; | ||
module.exports = exports['default']; | ||
function close_dangling_braces(template, cut_before) { | ||
var retained_template = template.slice(0, cut_before); | ||
var opening_braces = count_occurences('(', retained_template); | ||
var closing_braces = count_occurences(')', retained_template); | ||
var dangling_braces = opening_braces - closing_braces; | ||
while (dangling_braces > 0 && cut_before < template.length) { | ||
if (template[cut_before] === ')') { | ||
dangling_braces--; | ||
} | ||
cut_before++; | ||
} | ||
return template.slice(0, cut_before); | ||
} | ||
// Counts all occurences of a symbol in a string | ||
function count_occurences(symbol, string) { | ||
var count = 0; | ||
var _iteratorNormalCompletion5 = true; | ||
var _didIteratorError5 = false; | ||
var _iteratorError5 = undefined; | ||
try { | ||
for (var _iterator5 = (0, _getIterator3.default)(string), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { | ||
var character = _step5.value; | ||
if (character === symbol) { | ||
count++; | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError5 = true; | ||
_iteratorError5 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion5 && _iterator5.return) { | ||
_iterator5.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError5) { | ||
throw _iteratorError5; | ||
} | ||
} | ||
} | ||
return count; | ||
} | ||
//# sourceMappingURL=as you type.js.map |
@@ -6,2 +6,3 @@ 'use strict'; | ||
}); | ||
exports.FIRST_GROUP_PATTERN = undefined; | ||
@@ -13,2 +14,3 @@ var _getIterator2 = require('babel-runtime/core-js/get-iterator'); | ||
exports.default = format; | ||
exports.format_national_number_using_format = format_national_number_using_format; | ||
exports.format_national_number = format_national_number; | ||
@@ -69,3 +71,3 @@ exports.local_to_international_style = local_to_international_style; | ||
case 'International': | ||
var national_number = format_national_number(number, 'International', country_metadata); | ||
var national_number = format_national_number(number, 'International', false, country_metadata); | ||
return '+' + (0, _metadata3.get_phone_code)(country_metadata) + ' ' + national_number; | ||
@@ -77,3 +79,3 @@ | ||
case 'National': | ||
return format_national_number(number, 'National', country_metadata); | ||
return format_national_number(number, 'National', false, country_metadata); | ||
} | ||
@@ -86,22 +88,20 @@ } | ||
// group actually used in the pattern will be matched. | ||
var FIRST_GROUP_PATTERN = /(\$\d)/; | ||
var FIRST_GROUP_PATTERN = exports.FIRST_GROUP_PATTERN = /(\$\d)/; | ||
function format_national_number(number, format_as, country_metadata) { | ||
var format = choose_format_for_number((0, _metadata3.get_formats)(country_metadata), number); | ||
function format_national_number_using_format(number, format, international, enforce_national_prefix, country_metadata) { | ||
var national_prefix_formatting_rule = (0, _metadata3.get_format_national_prefix_formatting_rule)(format, country_metadata); | ||
if (!format) { | ||
return number; | ||
} | ||
var national_prefix_may_be_omitted = !enforce_national_prefix && (0, _metadata3.get_format_national_prefix_is_optional_when_formatting)(format, country_metadata); | ||
var pattern_to_match = new RegExp((0, _metadata3.get_format_pattern)(format)); | ||
if (!international && !national_prefix_may_be_omitted) { | ||
var _national_prefix_formatting_rule = (0, _metadata3.get_format_national_prefix_formatting_rule)(format, country_metadata); | ||
var national_prefix_formatting_rule = (0, _metadata3.get_format_national_prefix_formatting_rule)(format, country_metadata); | ||
var pattern_to_match = new RegExp((0, _metadata3.get_format_pattern)(format)); | ||
if (format_as === 'National' && !(0, _metadata3.get_format_national_prefix_is_optional_when_formatting)(format, country_metadata) && national_prefix_formatting_rule) { | ||
return number.replace(pattern_to_match, (0, _metadata3.get_format_format)(format).replace(FIRST_GROUP_PATTERN, national_prefix_formatting_rule)); | ||
return number.replace(pattern_to_match, (0, _metadata3.get_format_format)(format).replace(FIRST_GROUP_PATTERN, _national_prefix_formatting_rule)); | ||
} | ||
var formatted_number = number.replace(pattern_to_match, format_as === 'International' ? (0, _metadata3.get_format_international_format)(format) : (0, _metadata3.get_format_format)(format)); | ||
var formatted_number = number.replace(new RegExp((0, _metadata3.get_format_pattern)(format)), international ? (0, _metadata3.get_format_international_format)(format) : (0, _metadata3.get_format_format)(format)); | ||
if (format_as === 'International') { | ||
if (international) { | ||
return local_to_international_style(formatted_number); | ||
@@ -113,2 +113,12 @@ } | ||
function format_national_number(number, format_as, enforce_national_prefix, country_metadata) { | ||
var format = choose_format_for_number((0, _metadata3.get_formats)(country_metadata), number); | ||
if (!format) { | ||
return number; | ||
} | ||
return format_national_number_using_format(number, format, format_as === 'International', enforce_national_prefix, country_metadata); | ||
} | ||
function choose_format_for_number(available_formats, national_number) { | ||
@@ -115,0 +125,0 @@ var _iteratorNormalCompletion = true; |
@@ -0,1 +1,6 @@ | ||
0.1.5 / 29.11.2016 | ||
=================== | ||
* Better `asYouType` (better than Google's original "as you type" formatter) | ||
0.1.0 / 28.11.2016 | ||
@@ -2,0 +7,0 @@ =================== |
@@ -11,5 +11,5 @@ 'use strict' | ||
exports.as_you_type = require('./build/as you type') | ||
exports.asYouType = require('./build/as you type') | ||
exports.as_you_type = require('./build/as you type').default | ||
exports.asYouType = require('./build/as you type').default | ||
// exports['default'] = ... |
{ | ||
"name": "libphonenumber-js", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"description": "A simpler (and smaller) rewrite of Google Android's famous libphonenumber library", | ||
@@ -31,4 +31,4 @@ "main": "index.common.js", | ||
"test": "mocha --compilers js:babel-core/register --colors --bail --reporter spec test/ --recursive", | ||
"test-coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --compilers js:babel-core/register --colors --reporter dot test/ --recursive", | ||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --compilers js:babel-core/register --colors --reporter spec test/ --recursive", | ||
"test-coverage": "istanbul cover -x \"build/**\" node_modules/mocha/bin/_mocha -- --compilers js:babel-core/register --colors --reporter dot test/ --recursive", | ||
"test-travis": "istanbul cover -x \"build/**\" node_modules/mocha/bin/_mocha --report lcovonly -- --compilers js:babel-core/register --colors --reporter spec test/ --recursive", | ||
"browser-build": "WEBPACK_ENV=build webpack", | ||
@@ -35,0 +35,0 @@ "browser-build-dev": "WEBPACK_ENV=dev webpack --progress --colors --watch", |
@@ -7,24 +7,26 @@ # libphonenumber-js | ||
A simpler (and smaller) rewrite of Google Android's famous `libphonenumber` library. | ||
A simpler (and smaller) rewrite of Google Android's famous `libphonenumber` library: easy phone number parsing and formatting in javascript. | ||
## LibPhoneNumber | ||
`libphonenumber` is a phone number formatting and parsing library released by Google, originally developed for (and currently used in) Google's [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) mobile phone operating system. Obviously, implementing a rigorous phone number formatting and parsing library was crucial for the phone OS overall usability (back then, in the early 2000s, it was originally meant to be a phone after all, not just a SnapChat device). | ||
`libphonenumber` is a phone number formatting and parsing library released by Google, originally developed for (and currently used in) Google's [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) mobile phone operating system. Implementing a rigorous phone number formatting and parsing library was crucial for the phone OS overall usability (back then, in the early 2000s, it was originally meant to be a phone after all, not just a SnapChat device). | ||
`libphonenumber-js` is a simplified javascript port of the original `libphonenumber` library (written in C++ and Java because those are the programming languages used in Android OS). While `libphonenumber` has an [official javascript port](https://github.com/googlei18n/libphonenumber/tree/master/javascript) which is being maintained by Google, it is tightly coupled to Google's `closure` javascript utility framework. It still can be compiled into [one big bundle](http://stackoverflow.com/questions/18678031/how-to-host-the-google-libphonenumber-locally/) which weighs 220 KiloBytes — quite a size for a phone number input component. It [can be customized](https://github.com/leodido/i18n.phonenumbers.js) too in a sense of which countries metadata to include but that wouldn't be an option for a worldwide solution. | ||
`libphonenumber-js` is a simplified pure javascript port of the original `libphonenumber` library (written in C++ and Java because those are the programming languages used in Android OS). While `libphonenumber` has an [official javascript port](https://github.com/googlei18n/libphonenumber/tree/master/javascript) which is being maintained by Google, it is tightly coupled to Google's `closure` javascript utility framework. It still can be compiled into [one big bundle](http://stackoverflow.com/questions/18678031/how-to-host-the-google-libphonenumber-locally/) which weighs 220 KiloBytes — quite a size for a phone number input component. It [can be reduced](https://github.com/leodido/i18n.phonenumbers.js) to a specific set of countries only but that wouldn't be an option for a worldwide international solution. | ||
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 rewrite 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 slimmer 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 70 KiloBytes. | ||
## 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 | ||
* When formatting international numbers replaces all braces, dashes, etc with spaces (because that's the logical thing to do, and leaving braces in an international number isn't) | ||
* Better "as you type" formatting | ||
* 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) | ||
* Doesn't handle carrier codes: they're only used in Colombia and Brazil, and only when dialing within those countries from a mobile phone to a fixed line number (the locals surely already know those carrier codes by themselves) | ||
* Doesn't use `possibleDigits` data to speed up phone number pre-validation (it just skips to the regular expression check itself) | ||
* Assumes all phone numbers being `format`ted are internationally diallable, because that's the only type of phone numbers users are supposed to be inputting on websites (no one inputs short codes, emergency telephone numbers like `911`, etc.) | ||
* Doesn't parse phone numbers with extensions (again, this is not the type of phone numbers users should input on websites — they're supposed to input their personal mobile phone numbers, or home stationary phone numbers if they're living in an area where celltowers don't have a good signal, not their business/enterprise stationary phone numbers) | ||
* Doesn't use `possibleDigits` data to speed up phone number pre-validation (it just skips to the regular expression check itself) | ||
* Doesn't distinguish between fixed line, mobile, pager, voicemail, toll free and other XXth century bullsh*t | ||
* Doesn't format phone numbers for "out of country dialing", e.g. `011 ...` in the US (again, just use the `+...` notation accepted worldwide for mobile phones) | ||
* Doesn't parse `tel:...` URIs ([RFC 3966](https://www.ietf.org/rfc/rfc3966.txt)) because it's not relevant for user-facing web experience | ||
* When formatting international numbers replaces all braces, dashes, etc with spaces (because that's the logical thing to do, and leaving braces in an international number isn't) | ||
@@ -112,3 +114,3 @@ ## Installation | ||
Creates a formatter for partially entered phone number. The two-letter `country_code` is optional and if specified restricts the phone number being input to the specified country. The instance of this class has two methods: | ||
Creates a formatter for partially entered phone number. The two-letter `country_code` is optional and, if specified, restricts the phone number being input to the specified country. The instance of this class has two methods: | ||
@@ -115,0 +117,0 @@ * `input(text)` — takes any text and appends it to the input; returns the formatted phone number |
@@ -1,2 +0,2 @@ | ||
// This is a port of Google Android `libphonenumber`'s | ||
// This is an enhanced port of Google Android `libphonenumber`'s | ||
// `asyoutypeformatter.js` of 17th November, 2016. | ||
@@ -18,3 +18,2 @@ // | ||
get_format_national_prefix_formatting_rule, | ||
get_format_national_prefix_is_optional_when_formatting, | ||
get_format_leading_digits_patterns, | ||
@@ -39,2 +38,10 @@ get_metadata_by_country_phone_code | ||
{ | ||
FIRST_GROUP_PATTERN, | ||
format_national_number_using_format, | ||
local_to_international_style | ||
} | ||
from './format' | ||
import | ||
{ | ||
matches_entirely | ||
@@ -47,6 +54,4 @@ } | ||
const DIGIT_PLACEHOLDER = '\u2008' | ||
const DIGIT_PATTERN = new RegExp(DIGIT_PLACEHOLDER) | ||
const DIGIT_PLACEHOLDER_MATCHER = new RegExp(DIGIT_PLACEHOLDER) | ||
const SEPARATOR_BEFORE_NATIONAL_NUMBER = ' ' | ||
// A pattern that is used to match character classes in regular expressions. | ||
@@ -77,18 +82,7 @@ // An example of a character class is [1-4]. | ||
// A set of characters that, if found in a national prefix formatting rules, are | ||
// an indicator to us that we should separate the national prefix from the | ||
// number when formatting. | ||
const NATIONAL_PREFIX_SEPARATORS_PATTERN = /[- ]/ | ||
// This is the minimum length of national number accrued that is required to | ||
// trigger the formatter. The first element of the leadingDigitsPattern of | ||
// each numberFormat contains a regular expression that matches up to this | ||
// number of digits. | ||
// This is the minimum length of the leading digits of a phone number | ||
// to guarantee the first "leading digits pattern" for a phone number format | ||
// to be preemptive. | ||
const MIN_LEADING_DIGITS_LENGTH = 3 | ||
// A pattern that is used to determine if the national prefix formatting rule | ||
// has the first group only, i.e., does not start with the national prefix. | ||
// Note that the pattern explicitly allows for unbalanced parentheses. | ||
const FIRST_GROUP_ONLY_PREFIX_PATTERN = /^\(?\$1\)?$/ | ||
const VALID_INCOMPLETE_PHONE_NUMBER = | ||
@@ -111,2 +105,3 @@ '[' + PLUS_CHARS + ']{0,1}' + | ||
this.country_metadata = metadata.countries[country_code] | ||
this.initialize_possible_formats() | ||
} | ||
@@ -119,3 +114,3 @@ | ||
{ | ||
this.original_input += text | ||
// this.original_input += text | ||
@@ -161,107 +156,127 @@ // Parse input | ||
// If an out of position '+' sign detected | ||
// (or a second '+' sign) | ||
// (or a second '+' sign), | ||
// then just don't allow it being input. | ||
if (this.parsed_input) | ||
{ | ||
this.able_to_format = false | ||
return this.current_output | ||
} | ||
else | ||
{ | ||
this.parsed_input += character | ||
this.prefix_before_national_number = '+' | ||
} | ||
} | ||
// A digit then | ||
else | ||
{ | ||
this.parsed_input += character | ||
this.national_number += character | ||
} | ||
this.parsed_input += character | ||
// Try to format the parsed input | ||
if (!this.able_to_format) | ||
if (this.is_international()) | ||
{ | ||
// When we are unable to format because of reasons other than that | ||
// formatting chars have been entered, it can be due to really long IDDs or | ||
// NDDs. If that is the case, we might be able to do formatting again after | ||
// extracting them. | ||
if (this.is_international()) | ||
if (!this.country_phone_code) | ||
{ | ||
// If one looks at country phone codes | ||
// then he can notice that no one country phone code | ||
// is ever a (leftmost) substring of another country phone code. | ||
// So if a valid country code is extracted so far | ||
// then it means that this is the country code. | ||
if (this.extract_country_phone_code()) | ||
{ | ||
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted() | ||
// If the possible phone number formats | ||
// haven't been initialized during instance creation, | ||
// then do it. | ||
if (!this.country_code) | ||
{ | ||
this.initialize_possible_formats() | ||
} | ||
return '+' + this.country_phone_code | ||
} | ||
// Return raw phone number | ||
return this.parsed_input | ||
} | ||
else if (this.extract_longer_national_prefix()) | ||
} | ||
else | ||
{ | ||
if (!this.national_prefix) | ||
{ | ||
// Add an additional space to separate long NDD and national significant | ||
// number for readability. We don't set shouldAddSpaceAfterNationalPrefix_ | ||
// to true, since we don't want this to change later when we choose | ||
// formatting templates. | ||
this.prefix_before_national_number += SEPARATOR_BEFORE_NATIONAL_NUMBER | ||
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted() | ||
// Possibly extract a national prefix | ||
this.extract_national_prefix() | ||
} | ||
return this.parsed_input | ||
else if (!this.able_to_format) | ||
{ | ||
if (!this.extract_longer_national_prefix()) | ||
{ | ||
// Return raw phone number | ||
return this.parsed_input | ||
} | ||
} | ||
} | ||
// We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH | ||
// digits (the plus sign is counted as a digit as well for this purpose) have | ||
// been entered. | ||
// Format the next phone number digit | ||
// since the previously chose phone number format | ||
// still holds. | ||
// | ||
// This is done here because if `attempt_to_format_complete_phone_number` | ||
// was placed before this call then the `formatting_template` | ||
// wouldn't reflect the situation correctly (and would therefore be inconsistent) | ||
// | ||
const national_number_formatted_with_previous_format = this.format_next_national_number_digit(character) | ||
if (this.parsed_input.length < MIN_LEADING_DIGITS_LENGTH) | ||
// See if the input digits can be formatted properly already. If not, | ||
// use the results from format_next_national_number_digit(), which does formatting | ||
// based on the formatting pattern chosen. | ||
const formatted_number = this.attempt_to_format_complete_phone_number() | ||
if (formatted_number) | ||
{ | ||
return this.parsed_input | ||
return formatted_number | ||
} | ||
if (this.parsed_input.length === MIN_LEADING_DIGITS_LENGTH) | ||
this.filter_possible_formats_by_leading_digits() | ||
// If the previously chosen phone number format | ||
// didn't match the next digit being input | ||
// (leading digits). | ||
if (this.choose_another_format()) | ||
{ | ||
if (this.is_international()) | ||
{ | ||
this.expecting_country_calling_code = true | ||
} | ||
else | ||
{ | ||
// No IDD or plus sign is found, might be entering in national format. | ||
this.national_prefix = this.extract_national_prefix() | ||
return this.attempt_to_choose_formatting_pattern() | ||
} | ||
} | ||
// And a more appropriate phone number format | ||
// has been chosen for these `leading digits`, | ||
// then format the national phone number (so far) | ||
// using the newly selected phone number pattern. | ||
if (this.expecting_country_calling_code) | ||
{ | ||
if (this.extract_country_phone_code()) | ||
const formatted_national_number = this.reformat_national_number() | ||
if (formatted_national_number) | ||
{ | ||
this.expecting_country_calling_code = false | ||
return this.full_phone_number(formatted_national_number) | ||
} | ||
return this.prefix_before_national_number + this.national_number | ||
// Couldn't format the supplied national number | ||
// using the selected phone number pattern. | ||
// Return raw phone number. | ||
return this.parsed_input | ||
} | ||
if (this.possible_formats.length === 0) | ||
// If no new phone number format could be chosen, | ||
// then can't format the phone. | ||
if (!this.current_format) | ||
{ | ||
return this.attempt_to_choose_formatting_pattern() | ||
} | ||
this.able_to_format = false | ||
// The formatting patterns are already chosen. | ||
const national_number = this.input_national_number_digit(character) | ||
// See if the accrued digits can be formatted properly already. If not, | ||
// use the results from input_national_number_digit(), which does formatting | ||
// based on the formatting pattern chosen. | ||
const formatted_number = this.attempt_to_format_complete_phone_number() | ||
if (formatted_number) | ||
{ | ||
return formatted_number | ||
// Return raw phone number | ||
return this.parsed_input | ||
} | ||
this.narrow_down_possible_formats(this.national_number) | ||
if (this.refresh_format()) | ||
if (national_number_formatted_with_previous_format) | ||
{ | ||
return this.retype_national_number() | ||
return this.full_phone_number(national_number_formatted_with_previous_format) | ||
} | ||
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input | ||
// Couldn't format the supplied national number | ||
// using the selected phone number pattern. | ||
// Return raw phone number | ||
return this.parsed_input | ||
} | ||
@@ -271,4 +286,4 @@ | ||
{ | ||
// Input text so far, can contain any characters | ||
this.original_input = '' | ||
// // Input text so far, can contain any characters | ||
// this.original_input = '' | ||
@@ -281,9 +296,2 @@ // Input stripped of non-phone-number characters. | ||
this.expecting_country_calling_code = false | ||
// This contains anything that has been entered so far preceding the national | ||
// significant number, and it is formatted (e.g. with space inserted). For | ||
// example, this can contain IDD, country code, and/or NDD, etc. | ||
this.prefix_before_national_number = '' | ||
// This contains the national prefix that has been extracted. It contains only | ||
@@ -293,6 +301,11 @@ // digits without formatting. | ||
this.should_add_space_after_national_prefix = false | ||
this.national_number = '' | ||
this.country_phone_code = '' | ||
if (!this.country_code) | ||
{ | ||
this.country_metadata = undefined | ||
} | ||
this.clear_formatting() | ||
@@ -303,7 +316,8 @@ } | ||
{ | ||
// This indicates whether AsYouTypeFormatter is currently doing the formatting. | ||
this.able_to_format = true | ||
this.possible_formats = [] | ||
this.possible_formats = undefined | ||
this.current_format = undefined | ||
this.last_match_position = 0 | ||
@@ -313,20 +327,18 @@ | ||
// The pattern from numberFormat that is currently used to create formattingTemplate. | ||
this.current_formatting_pattern = undefined | ||
this.national_prefix_is_part_of_formatting_template = false | ||
} | ||
retype_national_number() | ||
// Format each digit of national phone number (so far) | ||
// using the newly selected phone number pattern. | ||
reformat_national_number() | ||
{ | ||
if (!this.national_number) | ||
{ | ||
return this.prefix_before_national_number | ||
} | ||
let national_number | ||
// Format each digit of national phone number (so far) | ||
// using the selected phone number pattern. | ||
let formatted_national_number | ||
for (let character of this.national_number) | ||
{ | ||
national_number = this.input_national_number_digit(character) | ||
formatted_national_number = this.format_next_national_number_digit(character) | ||
} | ||
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input | ||
return formatted_national_number | ||
} | ||
@@ -338,36 +350,7 @@ | ||
this.expecting_country_calling_code = false | ||
return this.attempt_to_choose_formatting_pattern() | ||
return this.format_national_number() | ||
} | ||
attempt_to_choose_formatting_pattern() | ||
initialize_possible_formats() | ||
{ | ||
// We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH | ||
// digits of national number (excluding national prefix) have been entered. | ||
if (this.national_number.length < MIN_LEADING_DIGITS_LENGTH) | ||
{ | ||
return this.full_phone_number(this.national_number) | ||
} | ||
this.refresh_possible_formats(this.national_number) | ||
// See if the accrued digits can be formatted properly already. | ||
const formatted_number = this.attempt_to_format_complete_phone_number() | ||
if (formatted_number) | ||
{ | ||
return formatted_number | ||
} | ||
if (this.refresh_format()) | ||
{ | ||
return this.retype_national_number() | ||
} | ||
return this.parsed_input | ||
} | ||
refresh_possible_formats(leading_digits) | ||
{ | ||
if (!this.country_metadata) | ||
@@ -378,5 +361,4 @@ { | ||
const national_prefix = get_national_prefix(this.country_metadata) | ||
this.possible_formats = get_formats(this.country_metadata).filter((format) => | ||
// Get all "eligible" phone number formats for this country | ||
this.available_formats = get_formats(this.country_metadata).filter((format) => | ||
{ | ||
@@ -386,11 +368,27 @@ return ELIGIBLE_FORMAT_PATTERN.test(get_format_international_format(format)) | ||
this.narrow_down_possible_formats(leading_digits) | ||
this.possible_formats = this.available_formats | ||
} | ||
narrow_down_possible_formats(leading_digits) | ||
filter_possible_formats_by_leading_digits() | ||
{ | ||
const index_of_leading_digits_pattern = leading_digits.length - MIN_LEADING_DIGITS_LENGTH | ||
const leading_digits = this.national_number | ||
this.possible_formats = this.possible_formats.filter((format) => | ||
// "leading digits" patterns start with a maximum 3 digits, | ||
// and then with each additional digit | ||
// a more precise "leading digits" pattern is specified. | ||
// They could make "leading digits" patterns start | ||
// with a maximum of a single digit, but they didn't, | ||
// so it's possible that some phone number formats | ||
// will be falsely rejected until there are at least | ||
// 3 digits in the national (significant) number being input. | ||
let index_of_leading_digits_pattern = leading_digits.length - MIN_LEADING_DIGITS_LENGTH | ||
if (index_of_leading_digits_pattern < 0) | ||
{ | ||
index_of_leading_digits_pattern = 0 | ||
} | ||
this.possible_formats = this.get_possible_formats().filter((format) => | ||
{ | ||
const leading_digits_pattern_count = get_format_leading_digits_patterns(format).length | ||
@@ -404,8 +402,20 @@ | ||
const suitable_leading_digits_pattern_index = Math.min(index_of_leading_digits_pattern, leading_digits_pattern_count - 1) | ||
const leading_digits_pattern = get_format_leading_digits_patterns(format)[suitable_leading_digits_pattern_index] | ||
return leading_digits.search(leading_digits_pattern) === 0 | ||
const leading_digits_pattern_index = Math.min(index_of_leading_digits_pattern, leading_digits_pattern_count - 1) | ||
const leading_digits_pattern = get_format_leading_digits_patterns(format)[leading_digits_pattern_index] | ||
return new RegExp('^' + leading_digits_pattern).test(leading_digits) | ||
}) | ||
} | ||
get_possible_formats() | ||
{ | ||
const leading_digits = this.national_number | ||
if (leading_digits.length <= MIN_LEADING_DIGITS_LENGTH) | ||
{ | ||
return this.available_formats | ||
} | ||
return this.possible_formats | ||
} | ||
// Check to see if there is an exact pattern match for these digits. If so, we | ||
@@ -416,13 +426,17 @@ // should use this instead of any other formatting template whose | ||
{ | ||
for (let format of this.possible_formats) | ||
for (let format of this.get_possible_formats()) | ||
{ | ||
const pattern = get_format_pattern(format) | ||
const pattern_matcher = new RegExp('^(?:' + pattern + ')$') | ||
const matcher = new RegExp('^(?:' + get_format_pattern(format) + ')$') | ||
if (pattern_matcher.test(this.national_number)) | ||
if (matcher.test(this.national_number)) | ||
{ | ||
this.should_add_space_after_national_prefix = NATIONAL_PREFIX_SEPARATORS_PATTERN.test(get_format_national_prefix_formatting_rule(format, this.country_metadata)) | ||
const formatted_national_number = format_national_number_using_format | ||
( | ||
this.national_number, | ||
format, | ||
this.is_international(), | ||
this.national_prefix, | ||
this.country_metadata | ||
) | ||
const formatted_national_number = this.national_number.replace(new RegExp(pattern, 'g'), this.get_format_format(format)) | ||
return this.full_phone_number(formatted_national_number) | ||
@@ -433,26 +447,16 @@ } | ||
// Combines the national number with any prefix (IDD/+ and country code or | ||
// national prefix) that was collected. A space will be inserted between them if | ||
// the current formatting template indicates this to be suitable. | ||
// Combines the national number with the appropriate prefix | ||
full_phone_number(formatted_national_number) | ||
{ | ||
if (this.should_add_space_after_national_prefix && | ||
this.prefix_before_national_number && | ||
this.prefix_before_national_number[this.prefix_before_national_number.length - 1] !== SEPARATOR_BEFORE_NATIONAL_NUMBER) | ||
if (this.is_international()) | ||
{ | ||
// We want to add a space after the national prefix if the national prefix | ||
// formatting rule indicates that this would normally be done, with the | ||
// exception of the case where we already appended a space because the NDD | ||
// was surprisingly long. | ||
return this.prefix_before_national_number + | ||
SEPARATOR_BEFORE_NATIONAL_NUMBER + | ||
formatted_national_number | ||
return '+' + this.country_phone_code + ' ' + formatted_national_number | ||
} | ||
return this.prefix_before_national_number + formatted_national_number | ||
return formatted_national_number | ||
} | ||
// Extracts the country calling code from the beginning of nationalNumber to | ||
// prefixBeforeNationalNumber when they are available, and places the remaining | ||
// input into nationalNumber. | ||
// Extracts the country calling code from the beginning | ||
// of the entered `national_number` (so far), | ||
// and places the remaining input into the `national_number`. | ||
extract_country_phone_code() | ||
@@ -483,11 +487,5 @@ { | ||
this.country_phone_code = country_phone_code | ||
this.national_number = number | ||
this.prefix_before_national_number += country_phone_code + SEPARATOR_BEFORE_NATIONAL_NUMBER | ||
// When we have successfully extracted the IDD, | ||
// the previously extracted national prefix | ||
// should be cleared because it is no longer valid. | ||
this.national_prefix = '' | ||
return this.country_metadata = get_metadata_by_country_phone_code(country_phone_code, metadata) | ||
@@ -501,19 +499,16 @@ } | ||
{ | ||
if (this.national_prefix) | ||
if (!this.national_prefix) | ||
{ | ||
// Put the extracted national prefix back to the national number | ||
// before attempting to extract a new national prefix. | ||
this.national_number = this.national_prefix + this.national_number | ||
// Remove the previously extracted national prefix from prefixBeforeNationalNumber. We | ||
// cannot simply set it to empty string because people sometimes incorrectly | ||
// enter national prefix after the country code, e.g. +44 (0)20-1234-5678. | ||
const index_of_previous_national_prefix = this.prefix_before_national_number.lastIndexOf(this.national_prefix) | ||
this.prefix_before_national_number = this.prefix_before_national_number.slice(0, index_of_previous_national_prefix) | ||
return | ||
} | ||
return this.national_prefix !== this.extract_national_prefix() | ||
// Put the extracted national prefix back to the national number | ||
// before attempting to extract a new national prefix. | ||
this.national_number = this.national_prefix + this.national_number | ||
const previously_extracted_national_prefix = this.national_prefix | ||
this.extract_national_prefix() | ||
return this.national_prefix !== previously_extracted_national_prefix | ||
} | ||
// Returns the national prefix extracted, or an empty string if it is not present. | ||
extract_national_prefix() | ||
@@ -525,6 +520,8 @@ { | ||
{ | ||
// Small performance optimization for NANPA countries | ||
// which can't have `1` (national prefix) as the | ||
// first digit of a national (significant) number | ||
if (this.is_NANPA_number_with_international_prefix()) | ||
{ | ||
national_number_starts_at = 1 | ||
this.prefix_before_national_number += '1' + SEPARATOR_BEFORE_NATIONAL_NUMBER | ||
} | ||
@@ -540,3 +537,2 @@ else if (get_national_prefix_for_parsing(this.country_metadata)) | ||
national_number_starts_at = matches[0].length | ||
this.prefix_before_national_number += this.national_number.substring(0, national_number_starts_at) | ||
} | ||
@@ -546,4 +542,5 @@ } | ||
this.national_prefix = this.national_number.slice(0, national_number_starts_at) | ||
this.national_number = this.national_number.slice(national_number_starts_at) | ||
return this.national_number.slice(0, national_number_starts_at) | ||
return this.national_prefix | ||
} | ||
@@ -569,23 +566,24 @@ | ||
refresh_format() | ||
choose_another_format() | ||
{ | ||
// When there are multiple available formats, the formatter uses the first | ||
// format where a formatting template could be created. | ||
for (let format of this.possible_formats) | ||
for (let format of this.get_possible_formats()) | ||
{ | ||
const pattern = get_format_pattern(format) | ||
if (this.current_formatting_pattern === pattern) | ||
// If this format is currently being used | ||
// and is still possible, then stick to it. | ||
if (this.current_format === format) | ||
{ | ||
return false | ||
return | ||
} | ||
// If this `format` is suitable for "as you type", | ||
// then extract the template from this format | ||
// and use it to format the phone number being input. | ||
if (this.create_formatting_template(format)) | ||
{ | ||
this.current_formatting_pattern = pattern | ||
this.current_format = format | ||
this.should_add_space_after_national_prefix = NATIONAL_PREFIX_SEPARATORS_PATTERN.test(get_format_national_prefix_formatting_rule(format, this.country_metadata)) | ||
// With a new formatting template, the matched position using the old | ||
// template needs to be reset. | ||
// With a new formatting template, the matched position | ||
// using the old template needs to be reset. | ||
this.last_match_position = 0 | ||
@@ -596,4 +594,2 @@ | ||
} | ||
this.able_to_format = false | ||
} | ||
@@ -615,14 +611,20 @@ | ||
.replace(CHARACTER_CLASS_PATTERN, '\\d') | ||
// Replace any standalone digit (not the one in d{}) with \d | ||
// Replace any standalone digit (not the one in `{}`) with \d | ||
.replace(STANDALONE_DIGIT_PATTERN, '\\d') | ||
return this.formatting_template = this.get_formatting_template(number_pattern, this.get_format_format(format)) | ||
} | ||
let number_format = this.get_format_format(format) | ||
this.national_prefix_is_part_of_formatting_template = false | ||
// Gets a formatting template which can be used to efficiently format a | ||
// partial number where digits are added one by one. | ||
get_formatting_template(number_pattern, number_format) | ||
{ | ||
// Creates a phone number consisting only of the digit 9 that matches the | ||
// numberPattern by applying the pattern to the longestPhoneNumber string. | ||
if (this.national_prefix) | ||
{ | ||
this.national_prefix_is_part_of_formatting_template = true | ||
const national_prefix_formatting_rule = get_format_national_prefix_formatting_rule(format, this.country_metadata) | ||
number_format = number_format.replace(FIRST_GROUP_PATTERN, national_prefix_formatting_rule) | ||
} | ||
// 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' | ||
@@ -641,3 +643,3 @@ | ||
return phone_number | ||
return this.formatting_template = phone_number | ||
// Formats the number according to numberFormat | ||
@@ -649,23 +651,24 @@ .replace(new RegExp(number_pattern, 'g'), number_format) | ||
input_national_number_digit(digit) | ||
format_next_national_number_digit(digit) | ||
{ | ||
if (this.formatting_template && this.formatting_template.slice(this.last_match_position).search(DIGIT_PATTERN) >= 0) | ||
// If there is room for more digits in current `formatting_template`, | ||
// then set the next digit in the `formatting_template`, | ||
// 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) | ||
{ | ||
const digit_pattern_start = this.formatting_template.search(DIGIT_PATTERN) | ||
this.formatting_template = this.formatting_template.replace(DIGIT_PATTERN, digit) | ||
const digit_pattern_start = this.formatting_template.search(DIGIT_PLACEHOLDER_MATCHER) | ||
this.formatting_template = this.formatting_template.replace(DIGIT_PLACEHOLDER_MATCHER, digit) | ||
this.last_match_position = digit_pattern_start | ||
return this.formatting_template.slice(0, digit_pattern_start + 1) | ||
// Return the formatted phone number so far | ||
return close_dangling_braces(this.formatting_template, digit_pattern_start + 1) | ||
} | ||
if (this.possible_formats.length === 1) | ||
{ | ||
// More digits are entered than we could handle, and there are | ||
// no other valid patterns to try. | ||
this.able_to_format = false | ||
} | ||
// else, we just reset the formatting pattern | ||
// More digits are entered than the current format could handle | ||
this.current_formatting_pattern = undefined | ||
return this.parsed_input | ||
// Reset the current format flag, | ||
// so that the new format will be chosen | ||
// in a subsequent `this.choose_another_format()` call | ||
// later in code. | ||
this.current_format = undefined | ||
} | ||
@@ -680,10 +683,5 @@ | ||
{ | ||
// // Always prefer international formatting rules over national ones, | ||
// // because national formatting rules could contain | ||
// // local formatting rules for numbers entered without area code. | ||
// get_format_international_format(format) | ||
if (this.is_international()) | ||
{ | ||
return get_format_international_format(format) | ||
return local_to_international_style(get_format_international_format(format)) | ||
} | ||
@@ -693,2 +691,39 @@ | ||
} | ||
} | ||
export function close_dangling_braces(template, cut_before) | ||
{ | ||
const retained_template = template.slice(0, cut_before) | ||
const opening_braces = count_occurences('(', retained_template) | ||
const closing_braces = count_occurences(')', retained_template) | ||
let dangling_braces = opening_braces - closing_braces | ||
while (dangling_braces > 0 && cut_before < template.length) | ||
{ | ||
if (template[cut_before] === ')') | ||
{ | ||
dangling_braces-- | ||
} | ||
cut_before++ | ||
} | ||
return template.slice(0, cut_before) | ||
} | ||
// Counts all occurences of a symbol in a string | ||
export function count_occurences(symbol, string) | ||
{ | ||
let count = 0 | ||
for (let character of string) | ||
{ | ||
if (character === symbol) | ||
{ | ||
count++ | ||
} | ||
} | ||
return count | ||
} |
@@ -69,3 +69,3 @@ // This is a port of Google Android `libphonenumber`'s | ||
case 'International': | ||
const national_number = format_national_number(number, 'International', country_metadata) | ||
const national_number = format_national_number(number, 'International', false, country_metadata) | ||
return `+${get_phone_code(country_metadata)} ${national_number}` | ||
@@ -77,3 +77,3 @@ | ||
case 'National': | ||
return format_national_number(number, 'National', country_metadata) | ||
return format_national_number(number, 'National', false, country_metadata) | ||
} | ||
@@ -86,21 +86,16 @@ } | ||
// group actually used in the pattern will be matched. | ||
const FIRST_GROUP_PATTERN = /(\$\d)/ | ||
export const FIRST_GROUP_PATTERN = /(\$\d)/ | ||
export function format_national_number(number, format_as, country_metadata) | ||
export function format_national_number_using_format(number, format, international, enforce_national_prefix, country_metadata) | ||
{ | ||
const format = choose_format_for_number(get_formats(country_metadata), number) | ||
const national_prefix_formatting_rule = get_format_national_prefix_formatting_rule(format, country_metadata) | ||
if (!format) | ||
const national_prefix_may_be_omitted = !enforce_national_prefix && get_format_national_prefix_is_optional_when_formatting(format, country_metadata) | ||
if (!international && !national_prefix_may_be_omitted) | ||
{ | ||
return number | ||
} | ||
const national_prefix_formatting_rule = get_format_national_prefix_formatting_rule(format, country_metadata) | ||
const pattern_to_match = new RegExp(get_format_pattern(format)) | ||
const pattern_to_match = new RegExp(get_format_pattern(format)) | ||
const national_prefix_formatting_rule = get_format_national_prefix_formatting_rule(format, country_metadata) | ||
if (format_as === 'National' && | ||
!get_format_national_prefix_is_optional_when_formatting(format, country_metadata) && | ||
national_prefix_formatting_rule) | ||
{ | ||
return number.replace(pattern_to_match, | ||
@@ -110,5 +105,5 @@ get_format_format(format).replace(FIRST_GROUP_PATTERN, national_prefix_formatting_rule)) | ||
const formatted_number = number.replace(pattern_to_match, format_as === 'International' ? get_format_international_format(format) : get_format_format(format)) | ||
const formatted_number = number.replace(new RegExp(get_format_pattern(format)), international ? get_format_international_format(format) : get_format_format(format)) | ||
if (format_as === 'International') | ||
if (international) | ||
{ | ||
@@ -121,2 +116,14 @@ return local_to_international_style(formatted_number) | ||
export function format_national_number(number, format_as, enforce_national_prefix, country_metadata) | ||
{ | ||
const format = choose_format_for_number(get_formats(country_metadata), number) | ||
if (!format) | ||
{ | ||
return number | ||
} | ||
return format_national_number_using_format(number, format, format_as === 'International', enforce_national_prefix, country_metadata) | ||
} | ||
function choose_format_for_number(available_formats, national_number) | ||
@@ -123,0 +130,0 @@ { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
1178449
3249
173