Exemplo n.º 1
0
        internal static bool allNumberGroupsRemainGrouped(PhoneNumberUtil util,
                                                          PhoneNumber number,
                                                          StringBuilder normalizedCandidate,
                                                          String[] formattedNumberGroups)
        {
            int fromIndex = 0;

            if (number.getCountryCodeSource() != PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY)
            {
                // First skip the country code if the normalized candidate contained it.
                String countryCode = number.getCountryCode().ToString();
                fromIndex = normalizedCandidate.ToString().IndexOf(countryCode) + countryCode.Length;
            }
            // 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.ToString().IndexOf(formattedNumberGroups[i], fromIndex, StringComparison.Ordinal);
                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. We get the region used for formatting
                    // information based on the country code in the phone number, rather than the number itself,
                    // as we do not need to distinguish between different countries with the same country
                    // calling code and this is faster.
                    String region = util.getRegionCodeForCountryCode(number.getCountryCode());
                    if (util.getNddPrefixForRegion(region, true) != null &&
                        char.IsDigit(normalizedCandidate[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. This is only important for countries with national prefixes.
                        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()));
        }
Exemplo n.º 2
0
        internal static bool containsMoreThanOneSlashInNationalNumber(PhoneNumber number, String candidate)
        {
            int firstSlashInBodyIndex = candidate.IndexOf('/');

            if (firstSlashInBodyIndex < 0)
            {
                // No slashes, this is okay.
                return(false);
            }
            // Now look for a second one.
            int secondSlashInBodyIndex = candidate.IndexOf('/', firstSlashInBodyIndex + 1);

            if (secondSlashInBodyIndex < 0)
            {
                // Only one slash, this is okay.
                return(false);
            }

            // If the first slash is after the country calling code, this is permitted.
            bool candidateHasCountryCode =
                (number.getCountryCodeSource() == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN ||
                 number.getCountryCodeSource() == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);

            if (candidateHasCountryCode &&
                PhoneNumberUtil.normalizeDigitsOnly(candidate.Substring(0, firstSlashInBodyIndex))
                .Equals(number.getCountryCode().ToString()))
            {
                // Any more slashes and this is illegal.
                return(candidate.Substring(secondSlashInBodyIndex + 1).Contains("/"));
            }
            return(true);
        }
Exemplo n.º 3
0
        internal static bool checkNumberGroupingIsValid(
            PhoneNumber number, String candidate, PhoneNumberUtil util, NumberGroupingChecker checker)
        {
            // TODO: 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);
            }
            // If this didn't pass, see if there are any alternate formats, and try them instead.
            PhoneMetadata alternateFormats =
                MetadataManager.getAlternateFormatsForCountry(number.getCountryCode());

            if (alternateFormats != null)
            {
                foreach (NumberFormat alternateFormat in alternateFormats.numberFormats())
                {
                    formattedNumberGroups = getNationalNumberGroups(util, number, alternateFormat);
                    if (checker.checkGroups(util, number, normalizedCandidate, formattedNumberGroups))
                    {
                        return(true);
                    }
                }
            }
            return(false);
        }
Exemplo n.º 4
0
        /**
         * Given a valid short number, determines whether it is carrier-specific (however, nothing is
         * implied about its validity). If it is important that the number is valid, then its validity
         * must first be checked using {@link #isValidShortNumber} or
         * {@link #isValidShortNumberForRegion}.
         *
         * @param number the valid short number to check
         * @return whether the short number is carrier-specific (assuming the input was a valid short
         *     number).
         */
        public bool isCarrierSpecific(PhoneNumber number)
        {
            List <String> regionCodes    = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
            String        regionCode     = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
            String        nationalNumber = phoneUtil.getNationalSignificantNumber(number);
            PhoneMetadata phoneMetadata  = MetadataManager.getShortNumberMetadataForRegion(regionCode);

            return((phoneMetadata != null) &&
                   (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getCarrierSpecific())));
        }
Exemplo n.º 5
0
        /**
         * Tests whether a short number matches a valid pattern. If a country calling code is shared by
         * multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify
         * the number is actually in use, which is impossible to tell by just looking at the number
         * itself. See {@link #isValidShortNumberForRegion(String, String)} for details.
         *
         * @param number the short number for which we want to test the validity
         * @return whether the short number matches a valid pattern
         */
        public bool isValidShortNumber(PhoneNumber number)
        {
            List <String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
            String        shortNumber = phoneUtil.getNationalSignificantNumber(number);
            String        regionCode  = getRegionCodeForShortNumberFromRegionList(number, regionCodes);

            if (regionCodes.Count > 1 && regionCode != null)
            {
                // If a matching region had been found for the phone number from among two or more regions,
                // then we have already implicitly verified its validity for that region.
                return(true);
            }
            return(isValidShortNumberForRegion(shortNumber, regionCode));
        }
Exemplo n.º 6
0
        /**
         * Check whether a short number is a possible number. If a country calling code is shared by
         * multiple regions, this returns true if it's possible in any of them. This provides a more
         * lenient check than {@link #isValidShortNumber}. See {@link
         * #isPossibleShortNumberForRegion(String, String)} for details.
         *
         * @param number the short number to check
         * @return whether the number is a possible short number
         */
        public bool isPossibleShortNumber(PhoneNumber number)
        {
            List <String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
            String        shortNumber = phoneUtil.getNationalSignificantNumber(number);

            foreach (String region in regionCodes)
            {
                PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(region);
                if (phoneUtil.isNumberPossibleForDesc(shortNumber, phoneMetadata.getGeneralDesc()))
                {
                    return(true);
                }
            }
            return(false);
        }
Exemplo n.º 7
0
        /**
         * Gets the expected cost category of a short number (however, nothing is implied about its
         * validity). If the country calling code is unique to a region, this method behaves exactly the
         * same as {@link #getExpectedCostForRegion(String, String)}. However, if the country calling
         * code is shared by multiple regions, then it returns the highest cost in the sequence
         * PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason for the position of
         * UNKNOWN_COST in this order is that if a number is UNKNOWN_COST in one region but STANDARD_RATE
         * or TOLL_FREE in another, its expected cost cannot be estimated as one of the latter since it
         * might be a PREMIUM_RATE number.
         *
         * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected cost
         * returned by this method will be STANDARD_RATE, since the NANPA countries share the same country
         * calling code.
         *
         * Note: If the region from which the number is dialed is known, it is highly preferable to call
         * {@link #getExpectedCostForRegion(String, String)} instead.
         *
         * @param number the short number for which we want to know the expected cost category
         * @return the highest expected cost category of the short number in the region(s) with the given
         *     country calling code
         */
        public ShortNumberCost getExpectedCost(PhoneNumber number)
        {
            List <String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());

            if (regionCodes.Count == 0)
            {
                return(ShortNumberCost.UNKNOWN_COST);
            }
            String shortNumber = phoneUtil.getNationalSignificantNumber(number);

            if (regionCodes.Count == 1)
            {
                return(getExpectedCostForRegion(shortNumber, regionCodes[0]));
            }
            ShortNumberCost cost = ShortNumberCost.TOLL_FREE;

            foreach (String regionCode in regionCodes)
            {
                ShortNumberCost costForRegion = getExpectedCostForRegion(shortNumber, regionCode);
                switch (costForRegion)
                {
                case ShortNumberCost.PREMIUM_RATE:
                    return(ShortNumberCost.PREMIUM_RATE);

                case ShortNumberCost.UNKNOWN_COST:
                    cost = ShortNumberCost.UNKNOWN_COST;
                    break;

                case ShortNumberCost.STANDARD_RATE:
                    if (cost != ShortNumberCost.UNKNOWN_COST)
                    {
                        cost = ShortNumberCost.STANDARD_RATE;
                    }
                    break;

                case ShortNumberCost.TOLL_FREE:
                    // Do nothing.
                    break;

                default:
                    logger.log(Level.SEVERE, "Unrecognised cost for region: " + costForRegion);
                    break;
                }
            }
            return(cost);
        }
Exemplo n.º 8
0
        internal static bool 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() != PhoneNumber.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.chooseFormattingRegexForNumber(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);
                }
                if (PhoneNumberUtil.formattingRuleHasFirstGroupOnly(
                        formatRule.getNationalPrefixFormattingRule()))
                {
                    // 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);
        }
Exemplo n.º 9
0
 public PhoneNumber mergeFrom(PhoneNumber other)
 {
     if (other.HasCountryCode())
     {
         setCountryCode(other.getCountryCode());
     }
     if (other.HasNationalNumber())
     {
         setNationalNumber(other.getNationalNumber());
     }
     if (other.HasExtension())
     {
         setExtension(other.getExtension());
     }
     if (other.HasItalianLeadingZero())
     {
         setItalianLeadingZero(other.isItalianLeadingZero());
     }
     if (other.HasNumberOfLeadingZeros())
     {
         setNumberOfLeadingZeros(other.getNumberOfLeadingZeros());
     }
     if (other.HasRawInput())
     {
         setRawInput(other.getRawInput());
     }
     if (other.HasCountryCodeSource())
     {
         setCountryCodeSource(other.getCountryCodeSource());
     }
     if (other.HasPreferredDomesticCarrierCode())
     {
         setPreferredDomesticCarrierCode(other.getPreferredDomesticCarrierCode());
     }
     return(this);
 }
Exemplo n.º 10
0
        /**
         * Parses a phone number from the {@code candidate} using {@link PhoneNumberUtil#parse} and
         * verifies it matches the requested {@link #leniency}. If parsing and verification succeed, a
         * corresponding {@link PhoneNumberMatch} is returned, otherwise this method returns null.
         *
         * @param candidate  the candidate match
         * @param offset  the offset of {@code candidate} within {@link #text}
         * @return  the parsed and validated phone number match, or null
         */
        private PhoneNumberMatch parseAndVerify(String candidate, int offset)
        {
            try {
                // Check the candidate doesn't contain any formatting which would indicate that it really
                // isn't a phone number.
                if (!MATCHING_BRACKETS.MatchWhole(candidate).Success || PUB_PAGES.Match(candidate).Success)
                {
                    return(null);
                }

                // If leniency is set to VALID or stricter, we also want to skip numbers that are surrounded
                // by Latin alphabetic characters, to skip cases like abc8005001234 or 8005001234def.
                if (leniency.CompareTo(PhoneNumberUtil.Leniency.VALID) >= 0)
                {
                    // If the candidate is not at the start of the text, and does not start with phone-number
                    // punctuation, check the previous character.
                    if (offset > 0 && !LEAD_CLASS.MatchBeginning(candidate).Success)
                    {
                        char previousChar = text[offset - 1];
                        // We return null if it is a latin letter or an invalid punctuation symbol.
                        if (isInvalidPunctuationSymbol(previousChar) || isLatinLetter(previousChar))
                        {
                            return(null);
                        }
                    }
                    int lastCharIndex = offset + candidate.Length;
                    if (lastCharIndex < text.Length)
                    {
                        char nextChar = text[lastCharIndex];
                        if (isInvalidPunctuationSymbol(nextChar) || isLatinLetter(nextChar))
                        {
                            return(null);
                        }
                    }
                }

                PhoneNumber number = phoneUtil.parseAndKeepRawInput(candidate, preferredRegion);

                // Check Israel * numbers: these are a special case in that they are four-digit numbers that
                // our library supports, but they can only be dialled with a leading *. Since we don't
                // actually store or detect the * in our phone number library, this means in practice we
                // detect most four digit numbers as being valid for Israel. We are considering moving these
                // numbers to ShortNumberInfo instead, in which case this problem would go away, but in the
                // meantime we want to restrict the false matches so we only allow these numbers if they are
                // preceded by a star. We enforce this for all leniency levels even though these numbers are
                // technically accepted by isPossibleNumber and isValidNumber since we consider it to be a
                // deficiency in those methods that they accept these numbers without the *.
                // TODO: Remove this or make it significantly less hacky once we've decided how to
                // handle these short codes going forward in ShortNumberInfo. We could use the formatting
                // rules for instance, but that would be slower.
                if (phoneUtil.getRegionCodeForCountryCode(number.getCountryCode()).Equals("IL") &&
                    phoneUtil.getNationalSignificantNumber(number).Length == 4 &&
                    (offset == 0 || (offset > 0 && text[offset - 1] != '*')))
                {
                    // No match.
                    return(null);
                }

                if (leniency.verify(number, candidate, phoneUtil))
                {
                    // We used parseAndKeepRawInput to create this number, but for now we don't return the extra
                    // values parsed. TODO: stop clearing all values here and switch all users over
                    // to using rawInput() rather than the rawString() of PhoneNumberMatch.
                    number.clearCountryCodeSource();
                    number.clearRawInput();
                    number.clearPreferredDomesticCarrierCode();
                    return(new PhoneNumberMatch(offset, candidate, number));
                }
            } catch (NumberParseException) {
                // ignore and continue
            }
            return(null);
        }