Browse Source

Initial work to port findNumbers to js, not yet complete

pull/2107/head
David Humphrey 8 years ago
parent
commit
21876631f7
6 changed files with 915 additions and 1 deletions
  1. +88
    -0
      javascript/i18n/phonenumbers/phonenumbermatch.js
  2. +52
    -0
      javascript/i18n/phonenumbers/phonenumbermatch_test.js
  3. +602
    -0
      javascript/i18n/phonenumbers/phonenumbermatcher.js
  4. +118
    -0
      javascript/i18n/phonenumbers/phonenumbermatcher_test.js
  5. +50
    -0
      javascript/i18n/phonenumbers/phonenumberutil.js
  6. +5
    -1
      javascript/i18n/phonenumbers/phonenumberutil_test.html

+ 88
- 0
javascript/i18n/phonenumbers/phonenumbermatch.js View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2011 The Libphonenumber Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('i18n.phonenumbers.PhoneNumberMatch');
/**
* The immutable match of a phone number within a piece of text. Matches may be found using
* {@link PhoneNumberUtil#findNumbers}.
*
* <p>A match consists of the {@linkplain #number() phone number} as well as the
* {@linkplain #start() start} and {@linkplain #end() end} offsets of the corresponding subsequence
* of the searched text. Use {@link #rawString()} to obtain a copy of the matched subsequence.
*
* <p>The following annotated example clarifies the relationship between the searched text, the
* match offsets, and the parsed number:
* <pre>
* CharSequence text = "Call me at +1 425 882-8080 for details.";
* String country = "US";
* PhoneNumberUtil util = PhoneNumberUtil.getInstance();
*
* // Find the first phone number match:
* PhoneNumberMatch m = util.findNumbers(text, country).iterator().next();
*
* // rawString() contains the phone number as it appears in the text.
* "+1 425 882-8080".equals(m.rawString());
*
* // start() and end() define the range of the matched subsequence.
* CharSequence subsequence = text.subSequence(m.start(), m.end());
* "+1 425 882-8080".contentEquals(subsequence);
*
* // number() returns the the same result as PhoneNumberUtil.{@link PhoneNumberUtil#parse parse()}
* // invoked on rawString().
* util.parse(m.rawString(), country).equals(m.number());
* </pre>
*/
i18n.phonenumbers.PhoneNumberMatch = function(start, rawString, number) {
if (start < 0) {
throw new Error('Start index must be >= 0.');
}
if (rawString == null) {
throw new Error('rawString must not be null');
}
if (number == null) {
throw new Error('number must not be null');
}
/** The start index into the text. */
this.start = start;
/** The raw substring matched. */
this.rawString = rawString;
/** The matched phone number. */
this.number = number;
/** The exclusive end index of the matched phone number within the searched text. */
this.end = start + rawString.length;
};
i18n.phonenumbers.PhoneNumberMatch.prototype.toString = function() {
return 'PhoneNumberMatch [' + this.start + ',' + this.end + ') ' + this.rawString;
};
/** XXX: do I care about this?
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PhoneNumberMatch)) {
return false;
}
PhoneNumberMatch other = (PhoneNumberMatch) obj;
return rawString.equals(other.rawString) && (start == other.start)
&& number.equals(other.number);
}
**/

+ 52
- 0
javascript/i18n/phonenumbers/phonenumbermatch_test.js View File

@ -0,0 +1,52 @@
goog.require('goog.testing.jsunit');
goog.require('i18n.phonenumbers.PhoneNumber');
goog.require('i18n.phonenumbers.PhoneNumberMatch');
goog.require('i18n.phonenumbers.PhoneNumberUtil');
var phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
var PhoneNumber = i18n.phonenumbers.PhoneNumber;
var PhoneNumberMatch = i18n.phonenumbers.PhoneNumberMatch
/**
* Tests the value type semantics. Equality and hash code must be based on the covered range and
* corresponding phone number. Range and number correctness are tested by
* {@link PhoneNumberMatcherTest}.
*/
function testPhoneNumberMatchValueTypeSemantics() {
var number = new PhoneNumber();
var match1 = new PhoneNumberMatch(10, "1 800 234 45 67", number);
var match2 = new PhoneNumberMatch(10, "1 800 234 45 67", number);
assertEquals(match1.start, match2.start);
assertEquals(match1.end, match2.end);
assertEquals(match1.number, match2.number);
assertEquals(match1.rawString, match2.rawString);
assertEquals("1 800 234 45 67", match1.rawString);
}
/**
* Tests the value type semantics for matches with a null number.
*/
function testPhoneNumberMatchIllegalArguments() {
var number;
try {
number = new PhoneNumberMatch(-110, "1 800 234 45 67", new PhoneNumber());
fail();
} catch (e) { /* success */ }
try {
number = new PhoneNumberMatch(10, "1 800 234 45 67", null);
fail();
} catch (e) { /* success */ }
try {
number = new PhoneNumberMatch(10, null, new PhoneNumber());
fail();
} catch (e) { /* success */ }
try {
number = new PhoneNumberMatch(10, null, null);
fail();
} catch (e) { /* success */ }
}

+ 602
- 0
javascript/i18n/phonenumbers/phonenumbermatcher.js
File diff suppressed because it is too large
View File


+ 118
- 0
javascript/i18n/phonenumbers/phonenumbermatcher_test.js View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2011 The Libphonenumber Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.require('goog.testing.jsunit');
goog.require('i18n.phonenumbers.PhoneNumber');
goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource');
goog.require('i18n.phonenumbers.PhoneNumberMatcher');
goog.require('i18n.phonenumbers.PhoneNumberMatch');
goog.require('i18n.phonenumbers.PhoneNumberUtil');
goog.require('i18n.phonenumbers.RegionCode');
var phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
var PhoneNumber = i18n.phonenumbers.PhoneNumber;
var PhoneNumberMatch = i18n.phonenumbers.PhoneNumberMatch;
var PhoneNumberMatcher = i18n.phonenumbers.PhoneNumberMatcher;
var CountryCodeSource = i18n.phonenumbers.PhoneNumber.CountryCodeSource;
var RegionCode = i18n.phonenumbers.RegionCode;
console.log('phoneUtil', phoneUtil);
console.log('PhoneNumberMatcher', PhoneNumberMatcher);
/**
* Tests numbers found by {@link PhoneNumberUtil#findNumbers(CharSequence, String)} in various
* textual contexts.
*
* @param number the number to test and the corresponding region code to use
*/
function doTestFindInContext(number, defaultCountry) {
findPossibleInContext(number, defaultCountry);
var parsed = phoneUtil.parse(number, defaultCountry);
if (phoneUtil.isValidNumber(parsed)) {
findValidInContext(number, defaultCountry);
}
}
/**
* Asserts that the expected match is non-null, and that the raw string and expected
* proto buffer are set appropriately.
*/
function assertMatchProperties(match, text, number, region) {
var expectedResult = phoneUtil.parse(number, region);
assertNotNull("Did not find a number in '" + text + "'; expected " + number, match);
assertEquals(expectedResult, match.number);
assertEquals(number, match.rawString);
}
function testContainsMoreThanOneSlashInNationalNumber() {
// A date should return true.
var number = new PhoneNumber();
number.setCountryCode(1);
number.setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY);
var candidate = "1/05/2013";
assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
// Here, the country code source thinks it started with a country calling code, but this is not
// the same as the part before the slash, so it's still true.
number = new PhoneNumber();
number.setCountryCode(274);
number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
candidate = "27/4/2013";
assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
// Now it should be false, because the first slash is after the country calling code.
number = new PhoneNumber();
number.setCountryCode(49);
number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN);
candidate = "49/69/2013";
assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
number = new PhoneNumber();
number.setCountryCode(49);
number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
candidate = "+49/69/2013";
assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
candidate = "+ 49/69/2013";
assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
candidate = "+ 49/69/20/13";
assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
// Here, the first group is not assumed to be the country calling code, even though it is the
// same as it, so this should return true.
number = new PhoneNumber();
number.setCountryCode(49);
number.setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY);
candidate = "49/69/2013";
assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate));
}
function testMatchesFoundWithMultipleSpaces() {
var number1 = "(415) 666-7777";
var number2 = "(800) 443-1223";
var text = number1 + " " + number2;
var iterator = phoneUtil.findNumbers(text, RegionCode.US);
var match = iterator.hasNext() ? iterator.next() : null;
assertMatchProperties(match, text, number1, RegionCode.US);
match = iterator.hasNext() ? iterator.next() : null;
assertMatchProperties(match, text, number2, RegionCode.US);
}

+ 50
- 0
javascript/i18n/phonenumbers/phonenumberutil.js View File

@ -102,6 +102,8 @@ i18n.phonenumbers.PhoneNumberUtil.NANPA_COUNTRY_CODE_ = 1;
*/
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_ = 2;
/** Flags to use when compiling regular expressions for phone numbers. */
i18n.phonenumbers.PhoneNumberUtil.REGEX_FLAGS = 'i'; // XXX: need ES6 regex for 'u' flag
/**
* The ITU says the maximum length should be 15, but we have found longer
@ -785,6 +787,18 @@ i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERNS_FOR_PARSING_ =
i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ + '#?|' +
'[- ]+([' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{1,5})#';
// For parsing, we are slightly more lenient in our interpretation than for matching. Here we
// allow "comma" and "semicolon" as possible extension indicators. When matching, these are
// hardly ever used to indicate this.
i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERNS_FOR_MATCHING =
i18n.phonenumbers.PhoneNumberUtil.RFC3966_EXTN_PREFIX_ +
i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ + '|' +
'[ \u00A0\\t,]*' +
'(?:e?xt(?:ensi(?:o\u0301?|\u00F3))?n?|\uFF45?\uFF58\uFF54\uFF4E?|' +
'[x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)' +
'[:\\.\uFF0E]?[ \u00A0\\t,-]*' +
i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ + '#?|' +
'[- ]+(' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + '{1,5})#';
/**
* Regexp of all known extension prefixes used by different regions followed by
@ -4248,6 +4262,21 @@ i18n.phonenumbers.PhoneNumberUtil.prototype.parseHelper_ =
return phoneNumber;
};
/**
* Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
* in that it always populates the raw_input field of the protocol buffer with numberToParse as
* well as the country_code_source field.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting such
* as +, ( and -, as well as a phone number extension.
* @param defaultRegion region that we are expecting the number to be from. This is only used if
* the number being parsed is not written in international format. The country calling code
* for the number in this case would be stored as that of the default region supplied.
* @return a phone number proto buffer filled with the parsed number
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parseAndKeepRawInput = function(numberToParse, defaultRegion) {
return this.parseHelper_(numberToParse, defaultRegion, true, true);
};
/**
* Converts numberToParse to a form that we can parse and write it to
@ -4522,6 +4551,27 @@ i18n.phonenumbers.PhoneNumberUtil.prototype.isNationalNumberSuffixOfTheOther_ =
firstNumberNationalNumber);
};
/**
* Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}. This
* is a shortcut for {@link #findNumbers(CharSequence, String, Leniency, long)
* getMatcher(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE)}.
*
* @param text the text to search for phone numbers, null for no text
* @param defaultRegion region that we are expecting the number to be from. This is only used if
* the number being parsed is not written in international format. The country_code for the
* number in this case would be stored as that of the default region supplied. May be null if
* only international numbers are expected.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.findNumbers = function(text, defaultRegion) {
if (!this.isValidRegionCode_(defaultRegion)) {
throw new Error('Invalid region code: ' + defaultRegion);
}
var maxTries = 9223372036854775807; // Long.MAX_VALUE is 9,223,372,036,854,775,807
var leniency = function(){};
return new PhoneNumberMatcher(this, text, defaultRegion, /*Leniency.VALID*/ leniency, maxTries);
};
/**
* Returns true if the number can be dialled from outside the region, or


+ 5
- 1
javascript/i18n/phonenumbers/phonenumberutil_test.html View File

@ -21,7 +21,7 @@ limitations under the License.
-->
<head>
<title>libphonenumber Unit Tests - i18n.phonenumbers - phonenumberutil.js</title>
<script src="../../../../closure-library/closure/goog/base.js"></script>
<script src="https://cdn.rawgit.com/google/closure-library/master/closure/goog/base.js"></script>
<script>
goog.require('goog.proto2.Message');
</script>
@ -31,6 +31,10 @@ limitations under the License.
<script src="regioncodefortesting.js"></script>
<script src="phonenumberutil.js"></script>
<script src="phonenumberutil_test.js"></script>
<script src="phonenumbermatch.js"></script>
<script src="phonenumbermatch_test.js"></script>
<script src="phonenumbermatcher.js"></script>
<script src="phonenumbermatcher_test.js"></script>
</head>
<body>
</body>


Loading…
Cancel
Save