Socket
Socket
Sign inDemoInstall

libphonenumber-js

Package Overview
Dependencies
Maintainers
1
Versions
392
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

libphonenumber-js - npm Package Compare versions

Comparing version 0.0.3 to 0.1.0

693

build/as you type.js
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _metadata = require('../metadata.min');
var _metadata2 = _interopRequireDefault(_metadata);
var _metadata3 = require('./metadata');
var _parse = require('./parse');
var _common = require('./common');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// The digits that have not been entered yet will be represented by a \u2008,
// the punctuation space.
// This is a port of Google Android `libphonenumber`'s
// `asyoutypeformatter.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/blob/8d21a365061de2ba0675c878a710a7b24f74d2ae/javascript/i18n/phonenumbers/asyoutypeformatter.js
var DIGIT_PLACEHOLDER = ' ';
var DIGIT_PATTERN = new RegExp(undefined.DIGIT_PLACEHOLDER);
var DIGIT_PATTERN = new RegExp(DIGIT_PLACEHOLDER);
var SEPARATOR_BEFORE_NATIONAL_NUMBER = ' ';
// A pattern that is used to match character classes in regular expressions.

@@ -15,3 +52,655 @@ // An example of a character class is [1-4].

// two-digit number, since the phone number can be as long as 15 digits.
var STANDALONE_DIGIT_PATTERN = '...';
var STANDALONE_DIGIT_PATTERN = /\d(?=[^,}][^,}])/g;
// A pattern that is used to determine if a numberFormat under availableFormats
// is eligible to be used by the AYTF. It is eligible when the format element
// under numberFormat contains groups of the dollar sign followed by a single
// digit, separated by valid phone number punctuation. This prevents invalid
// punctuation (such as the star sign in Israeli star numbers) getting into the
// output of the AYTF.
var ELIGIBLE_FORMAT_PATTERN = new RegExp('^' + '[' + _parse.VALID_PUNCTUATION + ']*' + '(\\$\\d[' + _parse.VALID_PUNCTUATION + ']*)+' + '$');
// 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.
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 + ']+';
var VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i');
var as_you_type = function () {
function as_you_type(country_code) {
(0, _classCallCheck3.default)(this, as_you_type);
if (country_code) {
this.country_code = country_code;
this.country_metadata = _metadata2.default.countries[country_code];
}
this.clear();
}
(0, _createClass3.default)(as_you_type, [{
key: 'input',
value: function input(text) {
this.original_input += text;
// Parse input
var extracted_number = (0, _parse.extract_formatted_phone_number)(text, function (number) {
return (0, _common.matches_entirely)(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, number);
});
var _parse_phone_number = (0, _parse.parse_phone_number)(extracted_number);
var number = _parse_phone_number.number;
var is_international = _parse_phone_number.is_international;
// Special case for just the leading '+'
if (!extracted_number && text.indexOf('+') >= 0) {
is_international = true;
}
var parsed_input = '';
if (is_international) {
parsed_input += '+';
}
if (number) {
parsed_input += number;
}
// Feed the parsed input character-by-character
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(parsed_input), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var character = _step.value;
this.current_output = this.input_character(character);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return this.current_output;
}
}, {
key: 'input_character',
value: function input_character(character) {
if (character === '+') {
// If an out of position '+' sign detected
// (or a second '+' sign)
if (this.parsed_input) {
this.able_to_format = false;
} else {
this.parsed_input += character;
this.prefix_before_national_number = '+';
}
} else {
this.parsed_input += character;
this.national_number += 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.extract_country_phone_code()) {
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted();
}
} 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 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.
if (this.parsed_input.length < MIN_LEADING_DIGITS_LENGTH) {
return this.parsed_input;
}
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();
}
}
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 (this.possible_formats.length === 0) {
return this.attempt_to_choose_formatting_pattern();
}
// The formatting patterns are already chosen.
var 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.
var formatted_number = this.attempt_to_format_complete_phone_number();
if (formatted_number) {
return formatted_number;
}
this.narrow_down_possible_formats(this.national_number);
if (this.refresh_format()) {
return this.retype_national_number();
}
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input;
}
}, {
key: 'clear',
value: function clear() {
// Input text so far, can contain any characters
this.original_input = '';
// Input stripped of non-phone-number characters.
// Can only contain a possible leading '+' sign and digits.
this.parsed_input = '';
this.current_output = '';
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
// digits without formatting.
this.national_prefix = '';
this.should_add_space_after_national_prefix = false;
this.national_number = '';
this.clear_formatting();
}
}, {
key: 'clear_formatting',
value: function clear_formatting() {
// This indicates whether AsYouTypeFormatter is currently doing the formatting.
this.able_to_format = true;
this.possible_formats = [];
this.last_match_position = 0;
this.formatting_template = undefined;
// The pattern from numberFormat that is currently used to create formattingTemplate.
this.current_formatting_pattern = undefined;
}
}, {
key: 'retype_national_number',
value: function retype_national_number() {
if (!this.national_number) {
return this.prefix_before_national_number;
}
var national_number = void 0;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)(this.national_number), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var character = _step2.value;
national_number = this.input_national_number_digit(character);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input;
}
}, {
key: 'attempt_to_choose_formatting_pattern_with_national_prefix_extracted',
value: function attempt_to_choose_formatting_pattern_with_national_prefix_extracted() {
this.clear_formatting();
this.expecting_country_calling_code = false;
return this.attempt_to_choose_formatting_pattern();
}
}, {
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) {
if (!this.country_metadata) {
return;
}
var national_prefix = (0, _metadata3.get_national_prefix)(this.country_metadata);
var formats = (0, _metadata3.get_international_formats)(this.country_metadata);
if (formats.length === 0) {
formats = (0, _metadata3.get_formats)(this.country_metadata);
}
this.possible_formats = formats.filter(function (format) {
return ELIGIBLE_FORMAT_PATTERN.test((0, _metadata3.get_format_international_format)(format));
});
this.narrow_down_possible_formats(leading_digits);
}
}, {
key: 'narrow_down_possible_formats',
value: function narrow_down_possible_formats(leading_digits) {
var index_of_leading_digits_pattern = leading_digits.length - MIN_LEADING_DIGITS_LENGTH;
this.possible_formats = this.possible_formats.filter(function (format) {
var leading_digits_pattern_count = (0, _metadata3.get_format_leading_digits_patterns)(format).length;
// Keep everything that isn't restricted by leading digits.
if (leading_digits_pattern_count === 0) {
return true;
}
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;
});
}
// Check to see if there is an exact pattern match for these digits. If so, we
// should use this instead of any other formatting template whose
// leadingDigitsPattern also matches the input.
}, {
key: 'attempt_to_format_complete_phone_number',
value: function attempt_to_format_complete_phone_number() {
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = (0, _getIterator3.default)(this.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 + ')$');
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));
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);
}
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
}
// 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.
}, {
key: 'full_phone_number',
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;
}
return this.prefix_before_national_number + 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.
}, {
key: 'extract_country_phone_code',
value: function extract_country_phone_code() {
if (!this.national_number) {
return;
}
var _parse_phone_number_a = (0, _parse.parse_phone_number_and_country_phone_code)(this.parsed_input);
var country_phone_code = _parse_phone_number_a.country_phone_code;
var number = _parse_phone_number_a.number;
if (!country_phone_code) {
return;
}
// Check country restriction
if (this.country_code) {
if (country_phone_code !== (0, _metadata3.get_phone_code)(this.country_metadata)) {
// Invalid country phone code for the
// international phone number being input.
return;
}
}
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);
}
// Some national prefixes are a substring of others. If extracting the shorter
// national prefix doesn't result in a number we can format,
// we try to see if we can extract a longer version here.
}, {
key: 'extract_longer_national_prefix',
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);
}
return this.national_prefix !== this.extract_national_prefix();
}
// Returns the national prefix extracted, or an empty string if it is not present.
}, {
key: 'extract_national_prefix',
value: function extract_national_prefix() {
var national_number_starts_at = 0;
if (this.country_metadata) {
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)) {
var national_prefix_for_parsing = new RegExp('^(?:' + (0, _metadata3.get_national_prefix_for_parsing)(this.country_metadata) + ')');
var matches = this.national_number.match(national_prefix_for_parsing);
// Since some national prefix patterns are entirely optional, check that a
// national prefix could actually be extracted.
if (matches && matches[0]) {
national_number_starts_at = matches[0].length;
this.prefix_before_national_number += this.national_number.substring(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);
}
// Returns `true` if the current country is a NANPA country and the
// national number begins with the national prefix.
}, {
key: 'is_NANPA_number_with_international_prefix',
value: function is_NANPA_number_with_international_prefix() {
// For NANPA numbers beginning with 1[2-9], treat the 1 as the national
// prefix. The reason is that national significant numbers in NANPA always
// start with [2-9] after the national prefix. Numbers beginning with 1[01]
// can only be short/emergency numbers, which don't need the national prefix.
if ((0, _metadata3.get_phone_code)(this.country_metadata) !== 1) {
return false;
}
return this.national_number[0] === '1' && this.national_number[1] !== '0' && this.national_number[1] !== '1';
}
}, {
key: 'refresh_format',
value: function refresh_format() {
// When there are multiple available formats, the formatter uses the first
// format where a formatting template could be created.
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = (0, _getIterator3.default)(this.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.create_formatting_template(format)) {
this.current_formatting_pattern = pattern;
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.
this.last_match_position = 0;
return true;
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
this.able_to_format = false;
}
}, {
key: 'create_formatting_template',
value: function create_formatting_template(format) {
var number_pattern = (0, _metadata3.get_format_pattern)(format);
// The formatter doesn't format numbers when numberPattern contains '|', e.g.
// (20|3)\d{4}. In those cases we quickly return.
if (number_pattern.indexOf('|') >= 0) {
return;
}
number_pattern = number_pattern
// Replace anything in the form of [..] with \d
.replace(CHARACTER_CLASS_PATTERN, '\\d')
// Replace any standalone digit (not the one in d{}) with \d
.replace(STANDALONE_DIGIT_PATTERN, '\\d');
return this.formatting_template = this.get_formatting_template(number_pattern, this.get_format_format(format));
}
// Gets a formatting template which can be used to efficiently format a
// partial number where digits are added one by one.
}, {
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.
var longest_phone_number = '999999999999999';
var matches = longest_phone_number.match(number_pattern);
// This match will always succeed
var phone_number = matches[0];
// No formatting template can be created if the number of digits entered so
// far is longer than the maximum the current formatting rule can accommodate.
if (phone_number.length < this.national_number.length) {
return;
}
return phone_number
// Formats the number according to numberFormat
.replace(new RegExp(number_pattern, 'g'), number_format)
// Replaces each digit with character DIGIT_PLACEHOLDER
.replace(new RegExp('9', 'g'), DIGIT_PLACEHOLDER);
}
}, {
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);
this.last_match_position = digit_pattern_start;
return this.formatting_template.slice(0, 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
this.current_formatting_pattern = undefined;
return this.parsed_input;
}
}, {
key: 'is_international',
value: function is_international() {
return this.parsed_input[0] === '+';
}
}, {
key: 'get_format_format',
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, _metadata3.get_format_format)(format);
}
}]);
return as_you_type;
}();
exports.default = as_you_type;
module.exports = exports['default'];
//# sourceMappingURL=as you type.js.map

2

build/compress.js

@@ -35,3 +35,3 @@ "use strict";

// When changing this array also change getters in `./metadata.js`
var format_array = [format.pattern, format.format, format.leading_digits, format.national_prefix_formatting_rule, format.national_prefix_optional_when_formatting, format.international_format];
var format_array = [format.pattern, format.format, format.leading_digits_patterns, format.national_prefix_formatting_rule, format.national_prefix_optional_when_formatting, format.international_format];

@@ -38,0 +38,0 @@ trim_array(format_array);

@@ -21,2 +21,4 @@ 'use strict';

var _parse = require('./parse');
var _metadata3 = require('./metadata');

@@ -26,21 +28,50 @@

function format(number, format, third_argument) {
// This is a port of Google Android `libphonenumber`'s
// `phonenumberutil.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
function format(input, format, third_argument) {
// If the first argument object is expanded
if (typeof number === 'string') {
number = { phone: number, country: format };
format = third_argument;
if (typeof input === 'string') {
// If number is passed not as an object
if (typeof third_argument === 'string') {
input = { phone: input, country: format };
format = third_argument;
} else {
input = { phone: input };
}
}
var country_metadata = _metadata2.default.countries[number.country];
var country_metadata = _metadata2.default.countries[input.country];
var _parse_phone_number_a = (0, _parse.parse_phone_number_and_country_phone_code)(input.phone);
var country_phone_code = _parse_phone_number_a.country_phone_code;
var number = _parse_phone_number_a.number;
if (country_phone_code) {
// Check country restriction
if (input.country && country_phone_code !== (0, _metadata3.get_phone_code)(country_metadata)) {
return input.phone;
}
country_metadata = (0, _metadata3.get_metadata_by_country_phone_code)(country_phone_code, _metadata2.default);
}
if (!country_metadata) {
return input.phone;
}
switch (format) {
case 'International':
var national_number = format_national_number(number.phone, 'International', country_metadata);
var national_number = format_national_number(number, 'International', country_metadata);
return '+' + (0, _metadata3.get_phone_code)(country_metadata) + ' ' + national_number;
case 'International_plaintext':
return '+' + (0, _metadata3.get_phone_code)(country_metadata) + number.phone;
return '+' + (0, _metadata3.get_phone_code)(country_metadata) + input.phone;
case 'National':
return format_national_number(number.phone, 'National', country_metadata);
return format_national_number(number, 'National', country_metadata);
}

@@ -53,12 +84,15 @@ }

// group actually used in the pattern will be matched.
// This is a port of Google Android `libphonenumber`'s
// `phonenumberutil.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
var 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);
// When the `international_formats` exist, we use that to format national number
// for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
var available_formats = (0, _metadata3.get_international_formats)(country_metadata);
if (available_formats.length === 0 || format_as === 'National') {
available_formats = (0, _metadata3.get_formats)(country_metadata);
}
var format = choose_format_for_number(available_formats, number);
if (!format) {

@@ -68,3 +102,2 @@ return number;

var formatting_rule = (0, _metadata3.get_format_international_format)(format);
var pattern_to_match = new RegExp((0, _metadata3.get_format_pattern)(format));

@@ -75,6 +108,6 @@

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, formatting_rule.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, 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));

@@ -97,5 +130,5 @@ if (format_as === 'International') {

if ((0, _metadata3.get_format_leading_digits)(_format)) {
if ((0, _metadata3.get_format_leading_digits_patterns)(_format).length > 0) {
// The last leading_digits_pattern is used here, as it is the most detailed
var last_leading_digits_pattern = (0, _metadata3.get_format_leading_digits)(_format)[(0, _metadata3.get_format_leading_digits)(_format).length - 1];
var last_leading_digits_pattern = (0, _metadata3.get_format_leading_digits_patterns)(_format)[(0, _metadata3.get_format_leading_digits_patterns)(_format).length - 1];

@@ -102,0 +135,0 @@ if (national_number.search(last_leading_digits_pattern) !== 0) {

@@ -140,3 +140,3 @@ 'use strict';

pattern: number_format.$.pattern,
leading_digits: number_format.leadingDigits ? number_format.leadingDigits.map(function (leading_digits) {
leading_digits_patterns: number_format.leadingDigits ? number_format.leadingDigits.map(function (leading_digits) {
return leading_digits.replace(/\s/g, '');

@@ -147,3 +147,3 @@ }) : undefined,

format: number_format.format[0],
international_format: number_format.intlFormat && number_format.intlFormat[0] !== 'NA' ? number_format.intlFormat : undefined
international_format: number_format.intlFormat ? number_format.intlFormat[0] : undefined
};

@@ -331,3 +331,22 @@ });

//
// `libphonenumber/BuildMetadataFromXml.java` was used as a reference.
// https://github.com/googlei18n/libphonenumber/blob/master/tools/java/common/src/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
//
// There are three Xml metadata files in Google's `libphonenumber`:
//
// * PhoneNumberMetadata.xml — core data, used both for parse/format and "as you type"
//
// * PhoneNumberAlternateFormats.xml — alternative phone number formats.
// is presumably used for parsing phone numbers
// written in "alternative" formats.
// is not used by "as you type"
// presumably because of formats ambiguity
// when combined with the core data.
// this metadata is not used in this library
// as there's no clear description on what to do with it
// and how it works in the original `libphonenumber` code.
//
// * ShortNumberMetadata.xml — emergency numbers, etc. not used in this library.
//
module.exports = exports['default'];
//# sourceMappingURL=generate.js.map

@@ -1,2 +0,2 @@

"use strict";
'use strict';

@@ -9,2 +9,3 @@ Object.defineProperty(exports, "__esModule", {

exports.get_formats = get_formats;
exports.get_international_formats = get_international_formats;
exports.get_national_prefix = get_national_prefix;

@@ -18,6 +19,7 @@ exports.get_national_prefix_formatting_rule = get_national_prefix_formatting_rule;

exports.get_format_format = get_format_format;
exports.get_format_leading_digits = get_format_leading_digits;
exports.get_format_leading_digits_patterns = get_format_leading_digits_patterns;
exports.get_format_national_prefix_formatting_rule = get_format_national_prefix_formatting_rule;
exports.get_format_national_prefix_is_optional_when_formatting = get_format_national_prefix_is_optional_when_formatting;
exports.get_format_international_format = get_format_international_format;
exports.get_metadata_by_country_phone_code = get_metadata_by_country_phone_code;
function get_phone_code(country_metadata) {

@@ -35,2 +37,8 @@ return country_metadata[0];

function get_international_formats(country_metadata) {
return get_formats(country_metadata).filter(function (format) {
return get_format_international_format(format) !== 'NA';
});
}
function get_national_prefix(country_metadata) {

@@ -76,4 +84,4 @@ return country_metadata[3];

function get_format_leading_digits(format_array) {
return format_array[2];
function get_format_leading_digits_patterns(format_array) {
return format_array[2] || [];
}

@@ -92,2 +100,13 @@

}
// Formatting information for regions which share
// a country calling code is contained by only one region
// for performance reasons. For example, for NANPA region
// ("North American Numbering Plan Administration",
// which includes USA, Canada, Cayman Islands, Bahamas, etc)
// it will be contained in the metadata for `US`.
function get_metadata_by_country_phone_code(country_phone_code, metadata) {
var country_code = metadata.country_phone_code_to_countries[country_phone_code][0];
return metadata.countries[country_code];
}
//# sourceMappingURL=metadata.js.map

@@ -6,2 +6,3 @@ 'use strict';

});
exports.VALID_PUNCTUATION = exports.VALID_DIGITS = exports.PLUS_CHARS = undefined;

@@ -20,4 +21,6 @@ var _getIterator2 = require('babel-runtime/core-js/get-iterator');

exports.extract_possible_number = extract_possible_number;
exports.extract_country_phone_code = extract_country_phone_code;
exports.get_metadata_by_country_phone_code = get_metadata_by_country_phone_code;
exports.is_viable_phone_number = is_viable_phone_number;
exports.extract_formatted_phone_number = extract_formatted_phone_number;
exports.parse_phone_number = parse_phone_number;
exports.parse_phone_number_and_country_phone_code = parse_phone_number_and_country_phone_code;
exports.strip_national_prefix = strip_national_prefix;

@@ -37,2 +40,132 @@ exports.find_country_code = find_country_code;

var PLUS_CHARS = exports.PLUS_CHARS = '++';
// Digits accepted in phone numbers
// (ascii, fullwidth, arabic-indic, and eastern arabic digits).
// This is a port of Google Android `libphonenumber`'s
// `phonenumberutil.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
var VALID_DIGITS = exports.VALID_DIGITS = '0-90-9٠-٩۰-۹';
// Regular expression of acceptable punctuation found in phone numbers. This
// excludes punctuation found as a leading character only. This consists of dash
// characters, white space characters, full stops, slashes, square brackets,
// parentheses and tildes. It also includes the letter 'x' as that is found as a
// placeholder for carrier information in some phone numbers. Full-width
// variants are also present.
var VALID_PUNCTUATION = exports.VALID_PUNCTUATION = '-x‐-―−ー--/  ­​⁠ ' + '()()[].\\[\\]/~⁓∼~';
// Regular expression of viable phone numbers. This is location independent.
// Checks we have at least three leading digits, and only valid punctuation,
// alpha characters and digits in the phone number. Does not include extension
// data. The symbol 'x' is allowed here as valid punctuation since it is often
// used as a placeholder for carrier codes, for example in Brazilian phone
// numbers. We also allow multiple '+' characters at the start.
//
// Corresponds to the following:
// [digits]{minLengthNsn}|
// plus_sign*
// (([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
//
// The first reg-ex is to allow short numbers (two digits long) to be parsed if
// they are entered as "15" etc, but only if there is no punctuation in them.
// The second expression restricts the number of digits to three or more, but
// then allows them to be in international form, and to have alpha-characters
// and punctuation. We split up the two reg-exes here and combine them when
// creating the reg-ex VALID_PHONE_NUMBER_PATTERN itself so we can prefix it
// with ^ and append $ to each branch.
//
// Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
// (wtf did they mean by saying that; probably nothing)
//
var MIN_LENGTH_PHONE_NUMBER_PATTERN = '[' + VALID_DIGITS + ']{' + MIN_LENGTH_FOR_NSN + '}';
//
// And this is the second reg-exp:
// (see MIN_LENGTH_PHONE_NUMBER_PATTERN for a full description of this reg-exp)
//
var VALID_PHONE_NUMBER = '[' + PLUS_CHARS + ']{0,1}' + '(?:' + '[' + VALID_PUNCTUATION + ']*' + '[' + VALID_DIGITS + ']' + '){3,}' + '[' + VALID_PUNCTUATION + VALID_DIGITS + ']*';
// The combined regular expression for valid phone numbers:
//
var VALID_PHONE_NUMBER_PATTERN = new RegExp(
// Either a short two-digit-only phone number
'^' + MIN_LENGTH_PHONE_NUMBER_PATTERN + '$' + '|' +
// Or a longer fully parsed phone number (min 3 characters)
'^' + VALID_PHONE_NUMBER +
// screw phone number extensions
// '(?:' + EXTN_PATTERNS_FOR_PARSING + ')?' +
'$', 'i');
// This consists of the plus symbol, digits, and arabic-indic digits.
var PHONE_NUMBER_START_PATTERN = new RegExp('[' + PLUS_CHARS + VALID_DIGITS + ']');
// Regular expression of trailing characters that we want to remove. We remove
// all characters that are not alpha or numerical characters. The hash character
// is retained here, as it may signify the previous block was an extension.
var AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$');
var LEADING_PLUS_CHARS_PATTERN = new RegExp('^[' + PLUS_CHARS + ']+');
// These mappings map a character (key) to a specific digit that should
// replace it for normalization purposes. Non-European digits that
// may be used in phone numbers are mapped to a European equivalent.
var DIGIT_MAPPINGS = {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'0': '0', // Fullwidth digit 0
'1': '1', // Fullwidth digit 1
'2': '2', // Fullwidth digit 2
'3': '3', // Fullwidth digit 3
'4': '4', // Fullwidth digit 4
'5': '5', // Fullwidth digit 5
'6': '6', // Fullwidth digit 6
'7': '7', // Fullwidth digit 7
'8': '8', // Fullwidth digit 8
'9': '9', // Fullwidth digit 9
'٠': '0', // Arabic-indic digit 0
'١': '1', // Arabic-indic digit 1
'٢': '2', // Arabic-indic digit 2
'٣': '3', // Arabic-indic digit 3
'٤': '4', // Arabic-indic digit 4
'٥': '5', // Arabic-indic digit 5
'٦': '6', // Arabic-indic digit 6
'٧': '7', // Arabic-indic digit 7
'٨': '8', // Arabic-indic digit 8
'٩': '9', // Arabic-indic digit 9
'۰': '0', // Eastern-Arabic digit 0
'۱': '1', // Eastern-Arabic digit 1
'۲': '2', // Eastern-Arabic digit 2
'۳': '3', // Eastern-Arabic digit 3
'۴': '4', // Eastern-Arabic digit 4
'۵': '5', // Eastern-Arabic digit 5
'۶': '6', // Eastern-Arabic digit 6
'۷': '7', // Eastern-Arabic digit 7
'۸': '8', // Eastern-Arabic digit 8
'۹': '9' // Eastern-Arabic digit 9
};
// The maximum length of the country calling code.
var MAX_LENGTH_COUNTRY_CODE = 3;
// The minimum length of the national significant number.
var MIN_LENGTH_FOR_NSN = 2;
// The ITU says the maximum length should be 15,
// but one can find longer numbers in Germany.
var MAX_LENGTH_FOR_NSN = 17;
// We don't allow input strings for parsing to be longer than 250 chars.
// This prevents malicious input from consuming CPU.
var MAX_INPUT_STRING_LENGTH = 250;
var default_options = {

@@ -56,7 +189,2 @@ country: {}

// Returns `{ country, number }`
// This is a port of Google Android `libphonenumber`'s
// `phonenumberutil.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
function parse(text, options) {

@@ -80,13 +208,9 @@ if (typeof options === 'string') {

if (!text || text.length > MAX_INPUT_STRING_LENGTH) {
return {};
}
var formatted_phone_number = extract_formatted_phone_number(text);
text = extract_possible_number(text);
var _parse_phone_number_a = parse_phone_number_and_country_phone_code(formatted_phone_number);
var _extract_country_phon = extract_country_phone_code(text);
var country_phone_code = _parse_phone_number_a.country_phone_code;
var number = _parse_phone_number_a.number;
var country_phone_code = _extract_country_phon.country_phone_code;
var number = _extract_country_phon.number;
// Maybe invalid country phone code encountered

@@ -107,3 +231,3 @@

country_metadata = get_metadata_by_country_phone_code(country_phone_code);
country_metadata = (0, _metadata3.get_metadata_by_country_phone_code)(country_phone_code, _metadata2.default);
} else if (options.country.default || options.country.restrict) {

@@ -151,78 +275,2 @@ country = options.country.default || options.country.restrict;

var PLUS_CHARS = '++';
// Digits accepted in phone numbers
// (ascii, fullwidth, arabic-indic, and eastern arabic digits).
var VALID_DIGITS = '0-90-9٠-٩۰-۹';
// This consists of the plus symbol, digits, and arabic-indic digits.
var PHONE_NUMBER_START_PATTERN = new RegExp('[' + PLUS_CHARS + VALID_DIGITS + ']');
// Regular expression of trailing characters that we want to remove. We remove
// all characters that are not alpha or numerical characters. The hash character
// is retained here, as it may signify the previous block was an extension.
var AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$');
var LEADING_PLUS_CHARS_PATTERN = new RegExp('^[' + PLUS_CHARS + ']+');
// These mappings map a character (key) to a specific digit that should
// replace it for normalization purposes. Non-European digits that
// may be used in phone numbers are mapped to a European equivalent.
var DIGIT_MAPPINGS = {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'0': '0', // Fullwidth digit 0
'1': '1', // Fullwidth digit 1
'2': '2', // Fullwidth digit 2
'3': '3', // Fullwidth digit 3
'4': '4', // Fullwidth digit 4
'5': '5', // Fullwidth digit 5
'6': '6', // Fullwidth digit 6
'7': '7', // Fullwidth digit 7
'8': '8', // Fullwidth digit 8
'9': '9', // Fullwidth digit 9
'٠': '0', // Arabic-indic digit 0
'١': '1', // Arabic-indic digit 1
'٢': '2', // Arabic-indic digit 2
'٣': '3', // Arabic-indic digit 3
'٤': '4', // Arabic-indic digit 4
'٥': '5', // Arabic-indic digit 5
'٦': '6', // Arabic-indic digit 6
'٧': '7', // Arabic-indic digit 7
'٨': '8', // Arabic-indic digit 8
'٩': '9', // Arabic-indic digit 9
'۰': '0', // Eastern-Arabic digit 0
'۱': '1', // Eastern-Arabic digit 1
'۲': '2', // Eastern-Arabic digit 2
'۳': '3', // Eastern-Arabic digit 3
'۴': '4', // Eastern-Arabic digit 4
'۵': '5', // Eastern-Arabic digit 5
'۶': '6', // Eastern-Arabic digit 6
'۷': '7', // Eastern-Arabic digit 7
'۸': '8', // Eastern-Arabic digit 8
'۹': '9' // Eastern-Arabic digit 9
};
// The maximum length of the country calling code.
var MAX_LENGTH_COUNTRY_CODE = 3;
// The minimum length of the national significant number.
var MIN_LENGTH_FOR_NSN = 2;
// The ITU says the maximum length should be 15,
// but one can find longer numbers in Germany.
var MAX_LENGTH_FOR_NSN = 17;
// We don't allow input strings for parsing to be longer than 250 chars.
// This prevents malicious input from consuming CPU.
var MAX_INPUT_STRING_LENGTH = 250;
// Normalizes a string of characters representing a phone number.

@@ -287,4 +335,33 @@ // This converts wide-ascii and arabic-indic numerals to European numerals,

// Tries to extract a country calling code from a number
function extract_country_phone_code(number) {
// Checks to see if the string of characters could possibly be a phone number at
// all. At the moment, checks to see that the string begins with at least 2
// digits, ignoring any punctuation commonly found in phone numbers. This method
// does not require the number to be normalized in advance - but does assume
// that leading non-number symbols have been removed, such as by the method
// `extract_possible_number`.
//
function is_viable_phone_number(number) {
return number.length >= MIN_LENGTH_FOR_NSN && (0, _common.matches_entirely)(VALID_PHONE_NUMBER_PATTERN, number);
}
function extract_formatted_phone_number(text) {
var is_valid = arguments.length <= 1 || arguments[1] === undefined ? is_viable_phone_number : arguments[1];
if (!text || text.length > MAX_INPUT_STRING_LENGTH) {
return;
}
// Extracts a piece of text possibly containing a phone number
text = extract_possible_number(text);
if (is_valid(text)) {
return text;
}
}
// Parses a formatted phone number
// and returns `{ is_international, number }`
// where `number` is either national (significant) phone number
// or an international phone number with the leading '+' stripped.
function parse_phone_number(number) {
if (!number) {

@@ -294,11 +371,24 @@ return {};

// If this is not an international phone number,
// then don't extract country phone code.
if (!LEADING_PLUS_CHARS_PATTERN.test(number)) {
return { number: number };
}
var is_international = LEADING_PLUS_CHARS_PATTERN.test(number);
// Strip the leading '+' and remove non-digits
number = normalize(number.replace(LEADING_PLUS_CHARS_PATTERN, ''));
// Remove non-digits
// (and strip the possible leading '+')
number = normalize(number);
return { number: number, is_international: is_international };
}
// Parses a formatted phone number
// and returns `{ country_phone_code, number }`
// where `number` is the national (significant) phone number.
//
// (aka `maybeExtractCountryPhoneCode`)
//
function parse_phone_number_and_country_phone_code(_number) {
var _parse_phone_number = parse_phone_number(_number);
var number = _parse_phone_number.number;
var is_international = _parse_phone_number.is_international;
if (!number) {

@@ -308,2 +398,8 @@ return {};

// If this is not an international phone number,
// then don't extract country phone code.
if (!is_international) {
return { number: number };
}
// Country codes do not begin with a '0'

@@ -337,13 +433,2 @@ if (number[0] === '0') {

// Formatting information for regions which share
// a country calling code is contained by only one region
// for performance reasons. For example, for NANPA region
// ("North American Numbering Plan Administration",
// which includes USA, Canada, Cayman Islands, Bahamas, etc)
// it will be contained in the metadata for `US`.
function get_metadata_by_country_phone_code(country_phone_code) {
var country_code = _metadata2.default.country_phone_code_to_countries[country_phone_code][0];
return _metadata2.default.countries[country_code];
}
// Strips any national prefix (such as 0, 1) present in the number provided

@@ -350,0 +435,0 @@ function strip_national_prefix(number, country_metadata) {

@@ -1,2 +0,23 @@

"use strict";
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
exports.default = is_valid;
var _parse = require('./parse');
var _parse2 = _interopRequireDefault(_parse);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function is_valid(number, country_code) {
return (0, _keys2.default)((0, _parse2.default)(number, country_code)).length > 0;
}
module.exports = exports['default'];
//# sourceMappingURL=validate.js.map

@@ -0,1 +1,6 @@

0.1.0 / 28.11.2016
===================
* Added `asYouType` and `isValidNumber`.
0.0.3 / 24.11.2016

@@ -2,0 +7,0 @@ ===================

@@ -8,2 +8,5 @@ 'use strict'

// exports.is_valid_number = require('./build/validate')
// exports.isValidNumber = require('./build/validate')
// exports['default'] = ...

@@ -12,1 +12,8 @@ export

from './source/format'
// export
// {
// default as is_valid_number,
// default as isValidNumber
// }
// from './source/format'
{
"name": "libphonenumber-js",
"version": "0.0.3",
"version": "0.1.0",
"description": "A simpler (and smaller) rewrite of Google Android's famous libphonenumber library",

@@ -5,0 +5,0 @@ "main": "index.common.js",

@@ -9,4 +9,2 @@ # libphonenumber-js

**Work in Progress, will be released in the next few days**
## LibPhoneNumber

@@ -20,3 +18,3 @@

## Differences from Google's `libphonenumber`
## Difference from Google's `libphonenumber`

@@ -31,3 +29,4 @@ * Weighs less than 70 KiloBytes while `libphonenumber` bundle weighs about 220 KiloBytes

* 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 `+...` notaion accepted worldwide for mobile phones)
* 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

@@ -43,7 +42,14 @@ ## Installation

```js
import { parse } from 'libphonenumber-js'
import { parse, format, asYouType } from 'libphonenumber-js'
parse('8 (800) 555 35 35', 'RU') === { country: 'RU', phone: '8005553535' }
parse('8 (800) 555 35 35', 'RU')
// { country: 'RU', phone: '8005553535' }
format({ country: 'US', phone: '2133734253' }, 'International') === '+1-213-373-4253'
format('2133734253', 'US', 'International')
// '+1-213-373-4253'
new asYouType().input('+12133734')
// '+1 213 373 4'
new asYouType('US').input('2133734')
// '(213) 373-4'
```

@@ -92,5 +98,32 @@

### isValidNumber(number, country_code)
(aka `is_valid_number`)
This function is simply a wrapper for `parse`: if `parse` returns an empty object then the phone number is not valid.
```js
isValidNumber('+1-213-373-4253') === true
isValidNumber('+1-213-373') === false
isValidNumber('(213) 373-4253', 'US') === true
isValidNumber('(213) 37', 'US') === false
```
### `class` asYouType(country_code)
(aka `as_you_type`)
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:
* `input(text)` — takes any text and appends it to the input; returns the formatted phone number
* `clear()` — clears input
```js
new asYouType().input('+12133734') === '+1 213 373 4'
new asYouType('US').input('2133734') === '(213) 373-4'
```
## To do
* Maybe exclude `national_prefix` from metadata due to it not being used (superseded by the relevant regular expressions)
* Implement "As you type"

@@ -97,0 +130,0 @@ ## Contributing

@@ -0,4 +1,50 @@

// This is a port of Google Android `libphonenumber`'s
// `asyoutypeformatter.js` of 17th November, 2016.
//
// https://github.com/googlei18n/libphonenumber/blob/8d21a365061de2ba0675c878a710a7b24f74d2ae/javascript/i18n/phonenumbers/asyoutypeformatter.js
import metadata from '../metadata.min'
import
{
get_phone_code,
get_national_prefix,
get_national_prefix_for_parsing,
get_formats,
get_international_formats,
get_format_pattern,
get_format_format,
get_format_international_format,
get_format_national_prefix_formatting_rule,
get_format_national_prefix_is_optional_when_formatting,
get_format_leading_digits_patterns,
get_metadata_by_country_phone_code
}
from './metadata'
import
{
VALID_PUNCTUATION,
PLUS_SIGN,
PLUS_CHARS,
VALID_DIGITS,
extract_formatted_phone_number,
parse_phone_number,
parse_phone_number_and_country_phone_code
}
from './parse'
import
{
matches_entirely
}
from './common'
// The digits that have not been entered yet will be represented by a \u2008,
// the punctuation space.
const DIGIT_PLACEHOLDER = '\u2008'
const DIGIT_PATTERN = new RegExp(this.DIGIT_PLACEHOLDER)
const DIGIT_PATTERN = new RegExp(DIGIT_PLACEHOLDER)
const SEPARATOR_BEFORE_NATIONAL_NUMBER = ' '
// A pattern that is used to match character classes in regular expressions.

@@ -13,2 +59,615 @@ // An example of a character class is [1-4].

// two-digit number, since the phone number can be as long as 15 digits.
const STANDALONE_DIGIT_PATTERN = '...'
const STANDALONE_DIGIT_PATTERN = /\d(?=[^,}][^,}])/g
// A pattern that is used to determine if a numberFormat under availableFormats
// is eligible to be used by the AYTF. It is eligible when the format element
// under numberFormat contains groups of the dollar sign followed by a single
// digit, separated by valid phone number punctuation. This prevents invalid
// punctuation (such as the star sign in Israeli star numbers) getting into the
// output of the AYTF.
const ELIGIBLE_FORMAT_PATTERN = new RegExp
(
'^' +
'[' + VALID_PUNCTUATION + ']*' +
'(\\$\\d[' + VALID_PUNCTUATION + ']*)+' +
'$'
)
// 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.
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 =
'[' + PLUS_CHARS + ']{0,1}' +
'[' +
VALID_PUNCTUATION +
VALID_DIGITS +
']+'
const VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i')
export default class as_you_type
{
constructor(country_code)
{
if (country_code)
{
this.country_code = country_code
this.country_metadata = metadata.countries[country_code]
}
this.clear()
}
input(text)
{
this.original_input += text
// Parse input
let extracted_number = extract_formatted_phone_number(text, number => matches_entirely(VALID_INCOMPLETE_PHONE_NUMBER_PATTERN, number))
let { number, is_international } = parse_phone_number(extracted_number)
// Special case for just the leading '+'
if (!extracted_number && text.indexOf('+') >= 0)
{
is_international = true
}
let parsed_input = ''
if (is_international)
{
parsed_input += '+'
}
if (number)
{
parsed_input += number
}
// Feed the parsed input character-by-character
for (let character of parsed_input)
{
this.current_output = this.input_character(character)
}
return this.current_output
}
input_character(character)
{
if (character === '+')
{
// If an out of position '+' sign detected
// (or a second '+' sign)
if (this.parsed_input)
{
this.able_to_format = false
}
else
{
this.parsed_input += character
this.prefix_before_national_number = '+'
}
}
else
{
this.parsed_input += character
this.national_number += 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.extract_country_phone_code())
{
return this.attempt_to_choose_formatting_pattern_with_national_prefix_extracted()
}
}
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 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.
if (this.parsed_input.length < MIN_LEADING_DIGITS_LENGTH)
{
return this.parsed_input
}
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()
}
}
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 (this.possible_formats.length === 0)
{
return this.attempt_to_choose_formatting_pattern()
}
// 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
}
this.narrow_down_possible_formats(this.national_number)
if (this.refresh_format())
{
return this.retype_national_number()
}
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input
}
clear()
{
// Input text so far, can contain any characters
this.original_input = ''
// Input stripped of non-phone-number characters.
// Can only contain a possible leading '+' sign and digits.
this.parsed_input = ''
this.current_output = ''
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
// digits without formatting.
this.national_prefix = ''
this.should_add_space_after_national_prefix = false
this.national_number = ''
this.clear_formatting()
}
clear_formatting()
{
// This indicates whether AsYouTypeFormatter is currently doing the formatting.
this.able_to_format = true
this.possible_formats = []
this.last_match_position = 0
this.formatting_template = undefined
// The pattern from numberFormat that is currently used to create formattingTemplate.
this.current_formatting_pattern = undefined
}
retype_national_number()
{
if (!this.national_number)
{
return this.prefix_before_national_number
}
let national_number
for (let character of this.national_number)
{
national_number = this.input_national_number_digit(character)
}
return this.able_to_format ? this.full_phone_number(national_number) : this.parsed_input
}
attempt_to_choose_formatting_pattern_with_national_prefix_extracted()
{
this.clear_formatting()
this.expecting_country_calling_code = false
return this.attempt_to_choose_formatting_pattern()
}
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.
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)
{
return
}
const national_prefix = get_national_prefix(this.country_metadata)
let formats = get_international_formats(this.country_metadata)
if (formats.length === 0)
{
formats = get_formats(this.country_metadata)
}
this.possible_formats = formats.filter((format) =>
{
return ELIGIBLE_FORMAT_PATTERN.test(get_format_international_format(format))
})
this.narrow_down_possible_formats(leading_digits)
}
narrow_down_possible_formats(leading_digits)
{
const index_of_leading_digits_pattern = leading_digits.length - MIN_LEADING_DIGITS_LENGTH
this.possible_formats = this.possible_formats.filter((format) =>
{
const leading_digits_pattern_count = get_format_leading_digits_patterns(format).length
// Keep everything that isn't restricted by leading digits.
if (leading_digits_pattern_count === 0)
{
return true
}
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
})
}
// Check to see if there is an exact pattern match for these digits. If so, we
// should use this instead of any other formatting template whose
// leadingDigitsPattern also matches the input.
attempt_to_format_complete_phone_number()
{
for (let format of this.possible_formats)
{
const pattern = get_format_pattern(format)
const pattern_matcher = new RegExp('^(?:' + pattern + ')$')
if (pattern_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 = this.national_number.replace(new RegExp(pattern, 'g'), this.get_format_format(format))
return this.full_phone_number(formatted_national_number)
}
}
}
// 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.
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
}
return this.prefix_before_national_number + 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.
extract_country_phone_code()
{
if (!this.national_number)
{
return
}
const { country_phone_code, number } = parse_phone_number_and_country_phone_code(this.parsed_input)
if (!country_phone_code)
{
return
}
// Check country restriction
if (this.country_code)
{
if (country_phone_code !== get_phone_code(this.country_metadata))
{
// Invalid country phone code for the
// international phone number being input.
return
}
}
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)
}
// Some national prefixes are a substring of others. If extracting the shorter
// national prefix doesn't result in a number we can format,
// we try to see if we can extract a longer version here.
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.
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 this.national_prefix !== this.extract_national_prefix()
}
// Returns the national prefix extracted, or an empty string if it is not present.
extract_national_prefix()
{
let national_number_starts_at = 0
if (this.country_metadata)
{
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 (get_national_prefix_for_parsing(this.country_metadata))
{
var national_prefix_for_parsing = new RegExp('^(?:' + get_national_prefix_for_parsing(this.country_metadata) + ')')
var matches = this.national_number.match(national_prefix_for_parsing)
// Since some national prefix patterns are entirely optional, check that a
// national prefix could actually be extracted.
if (matches && matches[0])
{
national_number_starts_at = matches[0].length
this.prefix_before_national_number += this.national_number.substring(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)
}
// Returns `true` if the current country is a NANPA country and the
// national number begins with the national prefix.
is_NANPA_number_with_international_prefix()
{
// For NANPA numbers beginning with 1[2-9], treat the 1 as the national
// prefix. The reason is that national significant numbers in NANPA always
// start with [2-9] after the national prefix. Numbers beginning with 1[01]
// can only be short/emergency numbers, which don't need the national prefix.
if (get_phone_code(this.country_metadata) !== 1)
{
return false
}
return this.national_number[0] === '1' &&
this.national_number[1] !== '0' &&
this.national_number[1] !== '1'
}
refresh_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)
{
const pattern = get_format_pattern(format)
if (this.current_formatting_pattern === pattern)
{
return false
}
if (this.create_formatting_template(format))
{
this.current_formatting_pattern = pattern
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.
this.last_match_position = 0
return true
}
}
this.able_to_format = false
}
create_formatting_template(format)
{
let number_pattern = get_format_pattern(format)
// The formatter doesn't format numbers when numberPattern contains '|', e.g.
// (20|3)\d{4}. In those cases we quickly return.
if (number_pattern.indexOf('|') >= 0)
{
return
}
number_pattern = number_pattern
// Replace anything in the form of [..] with \d
.replace(CHARACTER_CLASS_PATTERN, '\\d')
// Replace any standalone digit (not the one in d{}) with \d
.replace(STANDALONE_DIGIT_PATTERN, '\\d')
return this.formatting_template = this.get_formatting_template(number_pattern, this.get_format_format(format))
}
// 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.
const longest_phone_number = '999999999999999'
const matches = longest_phone_number.match(number_pattern)
// This match will always succeed
const phone_number = matches[0]
// No formatting template can be created if the number of digits entered so
// far is longer than the maximum the current formatting rule can accommodate.
if (phone_number.length < this.national_number.length)
{
return
}
return phone_number
// Formats the number according to numberFormat
.replace(new RegExp(number_pattern, 'g'), number_format)
// Replaces each digit with character DIGIT_PLACEHOLDER
.replace(new RegExp('9', 'g'), DIGIT_PLACEHOLDER)
}
input_national_number_digit(digit)
{
if (this.formatting_template && this.formatting_template.slice(this.last_match_position).search(DIGIT_PATTERN) >= 0)
{
const digit_pattern_start = this.formatting_template.search(DIGIT_PATTERN)
this.formatting_template = this.formatting_template.replace(DIGIT_PATTERN, digit)
this.last_match_position = digit_pattern_start
return this.formatting_template.slice(0, 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
this.current_formatting_pattern = undefined
return this.parsed_input
}
is_international()
{
return this.parsed_input[0] === '+'
}
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 get_format_international_format(format)
}
return get_format_format(format)
}
}

@@ -22,3 +22,3 @@ export default function compress(input)

format.format,
format.leading_digits,
format.leading_digits_patterns,
format.national_prefix_formatting_rule,

@@ -25,0 +25,0 @@ format.national_prefix_optional_when_formatting,

@@ -11,35 +11,69 @@ // This is a port of Google Android `libphonenumber`'s

{
parse_phone_number_and_country_phone_code
}
from './parse'
import
{
get_phone_code,
get_formats,
get_international_formats,
get_format_pattern,
get_format_format,
get_format_leading_digits,
get_format_leading_digits_patterns,
get_format_national_prefix_formatting_rule,
get_format_national_prefix_is_optional_when_formatting,
get_format_international_format
get_format_international_format,
get_metadata_by_country_phone_code
}
from './metadata'
export default function format(number, format, third_argument)
export default function format(input, format, third_argument)
{
// If the first argument object is expanded
if (typeof number === 'string')
if (typeof input === 'string')
{
number = { phone: number, country: format }
format = third_argument
// If number is passed not as an object
if (typeof third_argument === 'string')
{
input = { phone: input, country: format }
format = third_argument
}
else
{
input = { phone: input }
}
}
const country_metadata = metadata.countries[number.country]
let country_metadata = metadata.countries[input.country]
const { country_phone_code, number } = parse_phone_number_and_country_phone_code(input.phone)
if (country_phone_code)
{
// Check country restriction
if (input.country && country_phone_code !== get_phone_code(country_metadata))
{
return input.phone
}
country_metadata = get_metadata_by_country_phone_code(country_phone_code, metadata)
}
if (!country_metadata)
{
return input.phone
}
switch (format)
{
case 'International':
const national_number = format_national_number(number.phone, 'International', country_metadata)
const national_number = format_national_number(number, 'International', country_metadata)
return `+${get_phone_code(country_metadata)} ${national_number}`
case 'International_plaintext':
return `+${get_phone_code(country_metadata)}${number.phone}`
return `+${get_phone_code(country_metadata)}${input.phone}`
case 'National':
return format_national_number(number.phone, 'National', country_metadata)
return format_national_number(number, 'National', country_metadata)
}

@@ -56,4 +90,13 @@ }

{
const format = choose_format_for_number(get_formats(country_metadata), number)
// When the `international_formats` exist, we use that to format national number
// for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
let available_formats = get_international_formats(country_metadata)
if (available_formats.length === 0 || format_as === 'National')
{
available_formats = get_formats(country_metadata)
}
const format = choose_format_for_number(available_formats, number)
if (!format)

@@ -64,3 +107,2 @@ {

const formatting_rule = get_format_international_format(format)
const pattern_to_match = new RegExp(get_format_pattern(format))

@@ -75,6 +117,6 @@

return number.replace(pattern_to_match,
formatting_rule.replace(FIRST_GROUP_PATTERN, national_prefix_formatting_rule))
get_format_format(format).replace(FIRST_GROUP_PATTERN, national_prefix_formatting_rule))
}
const formatted_number = number.replace(pattern_to_match, formatting_rule)
const formatted_number = number.replace(pattern_to_match, format_as === 'International' ? get_format_international_format(format) : get_format_format(format))

@@ -93,6 +135,6 @@ if (format_as === 'International')

{
if (get_format_leading_digits(format))
if (get_format_leading_digits_patterns(format).length > 0)
{
// The last leading_digits_pattern is used here, as it is the most detailed
const last_leading_digits_pattern = get_format_leading_digits(format)[get_format_leading_digits(format).length - 1]
const last_leading_digits_pattern = get_format_leading_digits_patterns(format)[get_format_leading_digits_patterns(format).length - 1]

@@ -99,0 +141,0 @@ if (national_number.search(last_leading_digits_pattern) !== 0)

@@ -47,2 +47,21 @@ import { parseString } from 'xml2js'

//
// `libphonenumber/BuildMetadataFromXml.java` was used as a reference.
// https://github.com/googlei18n/libphonenumber/blob/master/tools/java/common/src/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
//
// There are three Xml metadata files in Google's `libphonenumber`:
//
// * PhoneNumberMetadata.xml — core data, used both for parse/format and "as you type"
//
// * PhoneNumberAlternateFormats.xml — alternative phone number formats.
// is presumably used for parsing phone numbers
// written in "alternative" formats.
// is not used by "as you type"
// presumably because of formats ambiguity
// when combined with the core data.
// this metadata is not used in this library
// as there's no clear description on what to do with it
// and how it works in the original `libphonenumber` code.
//
// * ShortNumberMetadata.xml — emergency numbers, etc. not used in this library.
//
export default function(input)

@@ -194,7 +213,7 @@ {

pattern: number_format.$.pattern,
leading_digits: number_format.leadingDigits ? number_format.leadingDigits.map(leading_digits => leading_digits.replace(/\s/g, '')) : undefined,
leading_digits_patterns: number_format.leadingDigits ? number_format.leadingDigits.map(leading_digits => leading_digits.replace(/\s/g, '')) : undefined,
national_prefix_formatting_rule: national_prefix_formatting_rule(number_format.$.nationalPrefixFormattingRule, territory.$.nationalPrefix),
national_prefix_optional_when_formatting: number_format.$.nationalPrefixOptionalWhenFormatting,
format: number_format.format[0],
international_format: (number_format.intlFormat && number_format.intlFormat[0] !== 'NA') ? number_format.intlFormat : undefined
international_format: number_format.intlFormat ? number_format.intlFormat[0] : undefined
}))

@@ -201,0 +220,0 @@

@@ -16,2 +16,7 @@ export function get_phone_code(country_metadata)

export function get_international_formats(country_metadata)
{
return get_formats(country_metadata).filter(format => get_format_international_format(format) !== 'NA')
}
export function get_national_prefix(country_metadata)

@@ -66,5 +71,5 @@ {

export function get_format_leading_digits(format_array)
export function get_format_leading_digits_patterns(format_array)
{
return format_array[2]
return format_array[2] || []
}

@@ -85,2 +90,14 @@

return format_array[5] || get_format_format(format_array)
}
// Formatting information for regions which share
// a country calling code is contained by only one region
// for performance reasons. For example, for NANPA region
// ("North American Numbering Plan Administration",
// which includes USA, Canada, Cayman Islands, Bahamas, etc)
// it will be contained in the metadata for `US`.
export function get_metadata_by_country_phone_code(country_phone_code, metadata)
{
const country_code = metadata.country_phone_code_to_countries[country_phone_code][0]
return metadata.countries[country_code]
}

@@ -15,6 +15,151 @@ // This is a port of Google Android `libphonenumber`'s

get_national_prefix_transform_rule,
get_leading_digits
get_leading_digits,
get_metadata_by_country_phone_code
}
from './metadata'
export const PLUS_CHARS = '+\uFF0B'
// Digits accepted in phone numbers
// (ascii, fullwidth, arabic-indic, and eastern arabic digits).
export const VALID_DIGITS = '0-9\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9'
// Regular expression of acceptable punctuation found in phone numbers. This
// excludes punctuation found as a leading character only. This consists of dash
// characters, white space characters, full stops, slashes, square brackets,
// parentheses and tildes. It also includes the letter 'x' as that is found as a
// placeholder for carrier information in some phone numbers. Full-width
// variants are also present.
export const VALID_PUNCTUATION =
'-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F \u00A0\u00AD\u200B\u2060\u3000' +
'()\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E'
// Regular expression of viable phone numbers. This is location independent.
// Checks we have at least three leading digits, and only valid punctuation,
// alpha characters and digits in the phone number. Does not include extension
// data. The symbol 'x' is allowed here as valid punctuation since it is often
// used as a placeholder for carrier codes, for example in Brazilian phone
// numbers. We also allow multiple '+' characters at the start.
//
// Corresponds to the following:
// [digits]{minLengthNsn}|
// plus_sign*
// (([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
//
// The first reg-ex is to allow short numbers (two digits long) to be parsed if
// they are entered as "15" etc, but only if there is no punctuation in them.
// The second expression restricts the number of digits to three or more, but
// then allows them to be in international form, and to have alpha-characters
// and punctuation. We split up the two reg-exes here and combine them when
// creating the reg-ex VALID_PHONE_NUMBER_PATTERN itself so we can prefix it
// with ^ and append $ to each branch.
//
// Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
// (wtf did they mean by saying that; probably nothing)
//
const MIN_LENGTH_PHONE_NUMBER_PATTERN = '[' + VALID_DIGITS + ']{' + MIN_LENGTH_FOR_NSN + '}'
//
// And this is the second reg-exp:
// (see MIN_LENGTH_PHONE_NUMBER_PATTERN for a full description of this reg-exp)
//
const VALID_PHONE_NUMBER =
'[' + PLUS_CHARS + ']{0,1}' +
'(?:' +
'[' + VALID_PUNCTUATION + ']*' +
'[' + VALID_DIGITS + ']' +
'){3,}' +
'[' +
VALID_PUNCTUATION +
VALID_DIGITS +
']*'
// The combined regular expression for valid phone numbers:
//
const VALID_PHONE_NUMBER_PATTERN = new RegExp
(
// Either a short two-digit-only phone number
'^' +
MIN_LENGTH_PHONE_NUMBER_PATTERN +
'$' +
'|' +
// Or a longer fully parsed phone number (min 3 characters)
'^' +
VALID_PHONE_NUMBER +
// screw phone number extensions
// '(?:' + EXTN_PATTERNS_FOR_PARSING + ')?' +
'$'
,
'i')
// This consists of the plus symbol, digits, and arabic-indic digits.
const PHONE_NUMBER_START_PATTERN = new RegExp('[' + PLUS_CHARS + VALID_DIGITS + ']')
// Regular expression of trailing characters that we want to remove. We remove
// all characters that are not alpha or numerical characters. The hash character
// is retained here, as it may signify the previous block was an extension.
const AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$')
const LEADING_PLUS_CHARS_PATTERN = new RegExp('^[' + PLUS_CHARS + ']+')
// These mappings map a character (key) to a specific digit that should
// replace it for normalization purposes. Non-European digits that
// may be used in phone numbers are mapped to a European equivalent.
const DIGIT_MAPPINGS =
{
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'\uFF10': '0', // Fullwidth digit 0
'\uFF11': '1', // Fullwidth digit 1
'\uFF12': '2', // Fullwidth digit 2
'\uFF13': '3', // Fullwidth digit 3
'\uFF14': '4', // Fullwidth digit 4
'\uFF15': '5', // Fullwidth digit 5
'\uFF16': '6', // Fullwidth digit 6
'\uFF17': '7', // Fullwidth digit 7
'\uFF18': '8', // Fullwidth digit 8
'\uFF19': '9', // Fullwidth digit 9
'\u0660': '0', // Arabic-indic digit 0
'\u0661': '1', // Arabic-indic digit 1
'\u0662': '2', // Arabic-indic digit 2
'\u0663': '3', // Arabic-indic digit 3
'\u0664': '4', // Arabic-indic digit 4
'\u0665': '5', // Arabic-indic digit 5
'\u0666': '6', // Arabic-indic digit 6
'\u0667': '7', // Arabic-indic digit 7
'\u0668': '8', // Arabic-indic digit 8
'\u0669': '9', // Arabic-indic digit 9
'\u06F0': '0', // Eastern-Arabic digit 0
'\u06F1': '1', // Eastern-Arabic digit 1
'\u06F2': '2', // Eastern-Arabic digit 2
'\u06F3': '3', // Eastern-Arabic digit 3
'\u06F4': '4', // Eastern-Arabic digit 4
'\u06F5': '5', // Eastern-Arabic digit 5
'\u06F6': '6', // Eastern-Arabic digit 6
'\u06F7': '7', // Eastern-Arabic digit 7
'\u06F8': '8', // Eastern-Arabic digit 8
'\u06F9': '9' // Eastern-Arabic digit 9
}
// The maximum length of the country calling code.
const MAX_LENGTH_COUNTRY_CODE = 3
// The minimum length of the national significant number.
const MIN_LENGTH_FOR_NSN = 2
// The ITU says the maximum length should be 15,
// but one can find longer numbers in Germany.
const MAX_LENGTH_FOR_NSN = 17
// We don't allow input strings for parsing to be longer than 250 chars.
// This prevents malicious input from consuming CPU.
const MAX_INPUT_STRING_LENGTH = 250
const default_options =

@@ -63,11 +208,6 @@ {

if (!text || text.length > MAX_INPUT_STRING_LENGTH)
{
return {}
}
const formatted_phone_number = extract_formatted_phone_number(text)
text = extract_possible_number(text)
let { country_phone_code, number } = parse_phone_number_and_country_phone_code(formatted_phone_number)
let { country_phone_code, number } = extract_country_phone_code(text)
// Maybe invalid country phone code encountered

@@ -91,3 +231,3 @@ if (!country_phone_code && !number)

country_metadata = get_metadata_by_country_phone_code(country_phone_code)
country_metadata = get_metadata_by_country_phone_code(country_phone_code, metadata)
}

@@ -144,79 +284,2 @@ else if (options.country.default || options.country.restrict)

const PLUS_CHARS = '+\uFF0B'
// Digits accepted in phone numbers
// (ascii, fullwidth, arabic-indic, and eastern arabic digits).
const VALID_DIGITS = '0-9\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9'
// This consists of the plus symbol, digits, and arabic-indic digits.
const PHONE_NUMBER_START_PATTERN = new RegExp('[' + PLUS_CHARS + VALID_DIGITS + ']')
// Regular expression of trailing characters that we want to remove. We remove
// all characters that are not alpha or numerical characters. The hash character
// is retained here, as it may signify the previous block was an extension.
const AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + ']+$')
const LEADING_PLUS_CHARS_PATTERN = new RegExp('^[' + PLUS_CHARS + ']+')
// These mappings map a character (key) to a specific digit that should
// replace it for normalization purposes. Non-European digits that
// may be used in phone numbers are mapped to a European equivalent.
const DIGIT_MAPPINGS =
{
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'\uFF10': '0', // Fullwidth digit 0
'\uFF11': '1', // Fullwidth digit 1
'\uFF12': '2', // Fullwidth digit 2
'\uFF13': '3', // Fullwidth digit 3
'\uFF14': '4', // Fullwidth digit 4
'\uFF15': '5', // Fullwidth digit 5
'\uFF16': '6', // Fullwidth digit 6
'\uFF17': '7', // Fullwidth digit 7
'\uFF18': '8', // Fullwidth digit 8
'\uFF19': '9', // Fullwidth digit 9
'\u0660': '0', // Arabic-indic digit 0
'\u0661': '1', // Arabic-indic digit 1
'\u0662': '2', // Arabic-indic digit 2
'\u0663': '3', // Arabic-indic digit 3
'\u0664': '4', // Arabic-indic digit 4
'\u0665': '5', // Arabic-indic digit 5
'\u0666': '6', // Arabic-indic digit 6
'\u0667': '7', // Arabic-indic digit 7
'\u0668': '8', // Arabic-indic digit 8
'\u0669': '9', // Arabic-indic digit 9
'\u06F0': '0', // Eastern-Arabic digit 0
'\u06F1': '1', // Eastern-Arabic digit 1
'\u06F2': '2', // Eastern-Arabic digit 2
'\u06F3': '3', // Eastern-Arabic digit 3
'\u06F4': '4', // Eastern-Arabic digit 4
'\u06F5': '5', // Eastern-Arabic digit 5
'\u06F6': '6', // Eastern-Arabic digit 6
'\u06F7': '7', // Eastern-Arabic digit 7
'\u06F8': '8', // Eastern-Arabic digit 8
'\u06F9': '9' // Eastern-Arabic digit 9
}
// The maximum length of the country calling code.
const MAX_LENGTH_COUNTRY_CODE = 3
// The minimum length of the national significant number.
const MIN_LENGTH_FOR_NSN = 2
// The ITU says the maximum length should be 15,
// but one can find longer numbers in Germany.
const MAX_LENGTH_FOR_NSN = 17
// We don't allow input strings for parsing to be longer than 250 chars.
// This prevents malicious input from consuming CPU.
const MAX_INPUT_STRING_LENGTH = 250
// Normalizes a string of characters representing a phone number.

@@ -266,20 +329,61 @@ // This converts wide-ascii and arabic-indic numerals to European numerals,

// Tries to extract a country calling code from a number
export function extract_country_phone_code(number)
// Checks to see if the string of characters could possibly be a phone number at
// all. At the moment, checks to see that the string begins with at least 2
// digits, ignoring any punctuation commonly found in phone numbers. This method
// does not require the number to be normalized in advance - but does assume
// that leading non-number symbols have been removed, such as by the method
// `extract_possible_number`.
//
export function is_viable_phone_number(number)
{
if (!number)
return number.length >= MIN_LENGTH_FOR_NSN &&
matches_entirely(VALID_PHONE_NUMBER_PATTERN, number)
}
export function extract_formatted_phone_number(text, is_valid = is_viable_phone_number)
{
if (!text || text.length > MAX_INPUT_STRING_LENGTH)
{
return {}
return
}
// If this is not an international phone number,
// then don't extract country phone code.
if (!LEADING_PLUS_CHARS_PATTERN.test(number))
// Extracts a piece of text possibly containing a phone number
text = extract_possible_number(text)
if (is_valid(text))
{
return { number }
return text
}
}
// Strip the leading '+' and remove non-digits
number = normalize(number.replace(LEADING_PLUS_CHARS_PATTERN, ''))
// Parses a formatted phone number
// and returns `{ is_international, number }`
// where `number` is either national (significant) phone number
// or an international phone number with the leading '+' stripped.
export function parse_phone_number(number)
{
if (!number)
{
return {}
}
const is_international = LEADING_PLUS_CHARS_PATTERN.test(number)
// Remove non-digits
// (and strip the possible leading '+')
number = normalize(number)
return { number, is_international }
}
// Parses a formatted phone number
// and returns `{ country_phone_code, number }`
// where `number` is the national (significant) phone number.
//
// (aka `maybeExtractCountryPhoneCode`)
//
export function parse_phone_number_and_country_phone_code(_number)
{
const { number, is_international } = parse_phone_number(_number)
if (!number)

@@ -290,2 +394,9 @@ {

// If this is not an international phone number,
// then don't extract country phone code.
if (!is_international)
{
return { number }
}
// Country codes do not begin with a '0'

@@ -322,14 +433,2 @@ if (number[0] === '0')

// Formatting information for regions which share
// a country calling code is contained by only one region
// for performance reasons. For example, for NANPA region
// ("North American Numbering Plan Administration",
// which includes USA, Canada, Cayman Islands, Bahamas, etc)
// it will be contained in the metadata for `US`.
export function get_metadata_by_country_phone_code(country_phone_code)
{
const country_code = metadata.country_phone_code_to_countries[country_phone_code][0]
return metadata.countries[country_code]
}
// Strips any national prefix (such as 0, 1) present in the number provided

@@ -336,0 +435,0 @@ export function strip_national_prefix(number, country_metadata)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc