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())); }
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); }
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); }
/** * 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()))); }
/** * 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)); }
/** * 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); }
/** * 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); }
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); }
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); }
/** * 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); }