libphonenumber-js
Advanced tools
Comparing version
@@ -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
1178449
0.01%3249
2.82%173
1.17%