diff options
author | lararennie@google.com <lararennie@google.com@ee073f10-1060-11df-b6a4-87a95322a99c> | 2012-04-24 14:28:26 +0000 |
---|---|---|
committer | lararennie@google.com <lararennie@google.com@ee073f10-1060-11df-b6a4-87a95322a99c> | 2012-04-24 14:28:26 +0000 |
commit | 69bb4cf126435c905a7a9e3c4e08462dc9278f6a (patch) | |
tree | b5c32ca80a63e2b7430002ee9e879aff20d6fa62 /java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java | |
parent | 73c295ede9dbd2785754790331078a7b2e5fdbb4 (diff) | |
download | android_external_libphonenumbergoogle-69bb4cf126435c905a7a9e3c4e08462dc9278f6a.tar.gz android_external_libphonenumbergoogle-69bb4cf126435c905a7a9e3c4e08462dc9278f6a.tar.bz2 android_external_libphonenumbergoogle-69bb4cf126435c905a7a9e3c4e08462dc9278f6a.zip |
JAVA: libphonenumber v4.8 More RFC support, metadata updates (including geocoding), some refactoring.
git-svn-id: http://libphonenumber.googlecode.com/svn/trunk@440 ee073f10-1060-11df-b6a4-87a95322a99c
Diffstat (limited to 'java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java')
-rw-r--r-- | java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java | 261 |
1 files changed, 235 insertions, 26 deletions
diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java index d4bc575..ace437d 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java @@ -17,6 +17,11 @@ package com.google.i18n.phonenumbers; import com.google.i18n.phonenumbers.PhoneNumberUtil.Leniency; +import com.google.i18n.phonenumbers.PhoneNumberUtil.MatchType; +import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; +import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import java.lang.Character.UnicodeBlock; @@ -214,32 +219,6 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> { this.maxTries = maxTries; } - public boolean hasNext() { - if (state == State.NOT_READY) { - lastMatch = find(searchIndex); - if (lastMatch == null) { - state = State.DONE; - } else { - searchIndex = lastMatch.end(); - state = State.READY; - } - } - return state == State.READY; - } - - public PhoneNumberMatch next() { - // Check the state and find the next match as a side-effect if necessary. - if (!hasNext()) { - throw new NoSuchElementException(); - } - - // Don't retain that memory any longer than necessary. - PhoneNumberMatch result = lastMatch; - lastMatch = null; - state = State.NOT_READY; - return result; - } - /** * Attempts to find the next subsequence in the searched sequence on or after {@code searchIndex} * that represents a phone number. Returns the next match, null if none was found. @@ -453,6 +432,236 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> { } /** + * Small helper interface such that the number groups can be checked according to different + * criteria. + */ + interface NumberGroupingChecker { + /** + * Returns true if the groups of digits found in our candidate phone number match our + * expectations. + * + * @param number the original number we found when parsing + * @param normalizedCandidate the candidate number, normalized to only contain ASCII digits, + * but with non-digits (spaces etc) retained + * @param expectedNumberGroups the groups of digits that we would expect to see if we + * formatted this number + */ + boolean checkGroups(PhoneNumberUtil util, PhoneNumber number, + StringBuilder normalizedCandidate, String[] expectedNumberGroups); + } + + static boolean allNumberGroupsRemainGrouped(PhoneNumberUtil util, + PhoneNumber number, + StringBuilder normalizedCandidate, + String[] formattedNumberGroups) { + int fromIndex = 0; + // Check each group of consecutive digits are not broken into separate groupings in the + // {@code normalizedCandidate} string. + for (int i = 0; i < formattedNumberGroups.length; i++) { + // Fails if the substring of {@code normalizedCandidate} starting from {@code fromIndex} + // doesn't contain the consecutive digits in formattedNumberGroups[i]. + fromIndex = normalizedCandidate.indexOf(formattedNumberGroups[i], fromIndex); + if (fromIndex < 0) { + return false; + } + // Moves {@code fromIndex} forward. + fromIndex += formattedNumberGroups[i].length(); + if (i == 0 && fromIndex < normalizedCandidate.length()) { + // We are at the position right after the NDC. + if (Character.isDigit(normalizedCandidate.charAt(fromIndex))) { + // This means there is no formatting symbol after the NDC. In this case, we only + // accept the number if there is no formatting symbol at all in the number, except + // for extensions. + String nationalSignificantNumber = util.getNationalSignificantNumber(number); + return normalizedCandidate.substring(fromIndex - formattedNumberGroups[i].length()) + .startsWith(nationalSignificantNumber); + } + } + } + // The check here makes sure that we haven't mistakenly already used the extension to + // match the last group of the subscriber number. Note the extension cannot have + // formatting in-between digits. + return normalizedCandidate.substring(fromIndex).contains(number.getExtension()); + } + + static boolean allNumberGroupsAreExactlyPresent(PhoneNumberUtil util, + PhoneNumber number, + StringBuilder normalizedCandidate, + String[] formattedNumberGroups) { + String[] candidateGroups = + PhoneNumberUtil.NON_DIGITS_PATTERN.split(normalizedCandidate.toString()); + // Set this to the last group, skipping it if the number has an extension. + int candidateNumberGroupIndex = + number.hasExtension() ? candidateGroups.length - 2 : candidateGroups.length - 1; + // First we check if the national significant number is formatted as a block. + // We use contains and not equals, since the national significant number may be present with + // a prefix such as a national number prefix, or the country code itself. + if (candidateGroups.length == 1 || + candidateGroups[candidateNumberGroupIndex].contains( + util.getNationalSignificantNumber(number))) { + return true; + } + // Starting from the end, go through in reverse, excluding the first group, and check the + // candidate and number groups are the same. + for (int formattedNumberGroupIndex = (formattedNumberGroups.length - 1); + formattedNumberGroupIndex > 0 && candidateNumberGroupIndex >= 0; + formattedNumberGroupIndex--, candidateNumberGroupIndex--) { + if (!candidateGroups[candidateNumberGroupIndex].equals( + formattedNumberGroups[formattedNumberGroupIndex])) { + return false; + } + } + // Now check the first group. There may be a national prefix at the start, so we only check + // that the candidate group ends with the formatted number group. + return (candidateNumberGroupIndex >= 0 && + candidateGroups[candidateNumberGroupIndex].endsWith(formattedNumberGroups[0])); + } + + /** + * Helper method to get the national-number part of a number, formatted without any national + * prefix, and return it as a set of digit blocks that would be formatted together. + */ + private static String[] getNationalNumberGroups(PhoneNumberUtil util, PhoneNumber number, + NumberFormat formattingPattern) { + if (formattingPattern == null) { + // This will be in the format +CC-DG;ext=EXT where DG represents groups of digits. + String rfc3966Format = util.format(number, PhoneNumberFormat.RFC3966); + // We remove the extension part from the formatted string before splitting it into different + // groups. + int endIndex = rfc3966Format.indexOf(';'); + if (endIndex < 0) { + endIndex = rfc3966Format.length(); + } + // The country-code will have a '-' following it. + int startIndex = rfc3966Format.indexOf('-') + 1; + return rfc3966Format.substring(startIndex, endIndex).split("-"); + } else { + // We format the NSN only, and split that according to the separator. + String nationalSignificantNumber = util.getNationalSignificantNumber(number); + return util.formatNsnUsingPattern(nationalSignificantNumber, + formattingPattern, PhoneNumberFormat.RFC3966).split("-"); + } + } + + static boolean checkNumberGroupingIsValid( + PhoneNumber number, String candidate, PhoneNumberUtil util, NumberGroupingChecker checker) { + // TODO(lararennie,shaopengjia): Evaluate how this works for other locales (testing has been + // limited to NANPA regions) and optimise if necessary. + StringBuilder normalizedCandidate = + PhoneNumberUtil.normalizeDigits(candidate, true /* keep non-digits */); + String[] formattedNumberGroups = getNationalNumberGroups(util, number, null); + if (checker.checkGroups(util, number, normalizedCandidate, formattedNumberGroups)) { + return true; + } + return false; + } + + static boolean containsMoreThanOneSlash(String candidate) { + int firstSlashIndex = candidate.indexOf('/'); + return (firstSlashIndex > 0 && candidate.substring(firstSlashIndex + 1).contains("/")); + } + + static boolean containsOnlyValidXChars( + PhoneNumber number, String candidate, PhoneNumberUtil util) { + // The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the + // national significant number or (2) an extension sign, in which case they always precede the + // extension number. We assume a carrier code is more than 1 digit, so the first case has to + // have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1 'x' + // or 'X'. We ignore the character if it appears as the last character of the string. + for (int index = 0; index < candidate.length() - 1; index++) { + char charAtIndex = candidate.charAt(index); + if (charAtIndex == 'x' || charAtIndex == 'X') { + char charAtNextIndex = candidate.charAt(index + 1); + if (charAtNextIndex == 'x' || charAtNextIndex == 'X') { + // This is the carrier code case, in which the 'X's always precede the national + // significant number. + index++; + if (util.isNumberMatch(number, candidate.substring(index)) != MatchType.NSN_MATCH) { + return false; + } + // This is the extension sign case, in which the 'x' or 'X' should always precede the + // extension number. + } else if (!PhoneNumberUtil.normalizeDigitsOnly(candidate.substring(index)).equals( + number.getExtension())) { + return false; + } + } + } + return true; + } + + static boolean isNationalPrefixPresentIfRequired(PhoneNumber number, PhoneNumberUtil util) { + // First, check how we deduced the country code. If it was written in international format, then + // the national prefix is not required. + if (number.getCountryCodeSource() != CountryCodeSource.FROM_DEFAULT_COUNTRY) { + return true; + } + String phoneNumberRegion = + util.getRegionCodeForCountryCode(number.getCountryCode()); + PhoneMetadata metadata = util.getMetadataForRegion(phoneNumberRegion); + if (metadata == null) { + return true; + } + // Check if a national prefix should be present when formatting this number. + String nationalNumber = util.getNationalSignificantNumber(number); + NumberFormat formatRule = + util.chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber); + // To do this, we check that a national prefix formatting rule was present and that it wasn't + // just the first-group symbol ($1) with punctuation. + if ((formatRule != null) && formatRule.getNationalPrefixFormattingRule().length() > 0) { + if (formatRule.isNationalPrefixOptionalWhenFormatting()) { + // The national-prefix is optional in these cases, so we don't need to check if it was + // present. + return true; + } + // Remove the first-group symbol. + String candidateNationalPrefixRule = formatRule.getNationalPrefixFormattingRule(); + // We assume that the first-group symbol will never be _before_ the national prefix. + candidateNationalPrefixRule = + candidateNationalPrefixRule.substring(0, candidateNationalPrefixRule.indexOf("$1")); + candidateNationalPrefixRule = + PhoneNumberUtil.normalizeDigitsOnly(candidateNationalPrefixRule); + if (candidateNationalPrefixRule.length() == 0) { + // National Prefix not needed for this number. + return true; + } + // Normalize the remainder. + String rawInputCopy = PhoneNumberUtil.normalizeDigitsOnly(number.getRawInput()); + StringBuilder rawInput = new StringBuilder(rawInputCopy); + // Check if we found a national prefix and/or carrier code at the start of the raw input, and + // return the result. + return util.maybeStripNationalPrefixAndCarrierCode(rawInput, metadata, null); + } + return true; + } + + public boolean hasNext() { + if (state == State.NOT_READY) { + lastMatch = find(searchIndex); + if (lastMatch == null) { + state = State.DONE; + } else { + searchIndex = lastMatch.end(); + state = State.READY; + } + } + return state == State.READY; + } + + public PhoneNumberMatch next() { + // Check the state and find the next match as a side-effect if necessary. + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Don't retain that memory any longer than necessary. + PhoneNumberMatch result = lastMatch; + lastMatch = null; + state = State.NOT_READY; + return result; + } + + /** * Always throws {@link UnsupportedOperationException} as removal is not supported. */ public void remove() { |