Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions csharp/PhoneNumbers/PhoneNumberUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -869,8 +869,10 @@ public static string GetCountryMobileToken(int countryCallingCode)
/// should be stripped from the number. If this is false, they
/// will be left unchanged in the number.</param>
/// <returns>The normalized string version of the phone number.</returns>
#if !(NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER)
private static string NormalizeHelper(string number, Func<char, char> normalizationReplacements, bool removeNonMatches)
Comment on lines 869 to 873
=> NormalizeHelper(new StringBuilder(number), normalizationReplacements, removeNonMatches).ToString();
#endif

private static StringBuilder NormalizeHelper(StringBuilder number, Func<char, char> normalizationReplacements, bool removeNonMatches)
{
Expand Down Expand Up @@ -985,27 +987,26 @@ private static bool DescHasData(PhoneNumberDesc desc)
|| desc.HasNationalNumberPattern;
}

// Pre-filtered: excludes FIXED_LINE_OR_MOBILE (convenience aggregate) and UNKNOWN (non-type).
private static readonly PhoneNumberType[] s_checkablePhoneNumberTypes =
[
PhoneNumberType.FIXED_LINE, PhoneNumberType.MOBILE, PhoneNumberType.TOLL_FREE,
PhoneNumberType.PREMIUM_RATE, PhoneNumberType.SHARED_COST, PhoneNumberType.VOIP,
PhoneNumberType.PERSONAL_NUMBER, PhoneNumberType.PAGER, PhoneNumberType.UAN,
PhoneNumberType.VOICEMAIL,
];

/// <summary>
/// Returns the types we have metadata for based on the PhoneMetadata object passed in, which must
/// be non-null.
/// </summary>
/// <param name="metadata"></param>
/// <returns></returns>
private HashSet<PhoneNumberType> GetSupportedTypesForMetadata(PhoneMetadata metadata)
{
var types = new HashSet<PhoneNumberType>();
foreach (PhoneNumberType type in Enum.GetValues(typeof(PhoneNumberType)))
foreach (var type in s_checkablePhoneNumberTypes)
{
if (type == PhoneNumberType.FIXED_LINE_OR_MOBILE || type == PhoneNumberType.UNKNOWN)
{
// Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and represents that a
// particular number type can't be determined) or UNKNOWN (the non-type).
continue;
}
if (DescHasData(GetNumberDescByType(metadata, type)))
{
types.Add(type);
}
}
return types;
}
Expand Down Expand Up @@ -2305,6 +2306,14 @@ private int MaybeExtractCountryCode(string number, PhoneMetadata defaultRegionMe
nationalNumber, keepRawInput, phoneNumber);
}

private static bool StringBuilderStartsWith(StringBuilder sb, string value)
{
if (sb.Length < value.Length) return false;
for (var i = 0; i < value.Length; i++)
if (sb[i] != value[i]) return false;
return true;
}

// Same as the string overload above, but takes a StringBuilder workspace directly so callers
// that already hold a StringBuilder can avoid an extra string<->StringBuilder round-trip.
// The fullNumber buffer is always mutated (Normalize is unconditional, and any leading '+' or
Expand Down Expand Up @@ -2355,9 +2364,10 @@ private int MaybeExtractCountryCode(StringBuilder fullNumber, PhoneMetadata defa
// before and after.
var defaultCountryCode = defaultRegionMetadata.CountryCode;
var defaultCountryCodeString = defaultCountryCode.ToString();
var normalizedNumber = fullNumber.ToString();
if (normalizedNumber.StartsWith(defaultCountryCodeString, StringComparison.Ordinal))
// Avoid the ToString() allocation unless the prefix actually matches (common case: it won't).
if (StringBuilderStartsWith(fullNumber, defaultCountryCodeString))
{
var normalizedNumber = fullNumber.ToString();
var potentialNationalNumberString = normalizedNumber.Substring(defaultCountryCodeString.Length);
// Use a separate buffer for the strip-CC trial so callers that share fullNumber as
// their workspace are not corrupted when this branch decides not to commit.
Expand All @@ -2371,7 +2381,7 @@ private int MaybeExtractCountryCode(StringBuilder fullNumber, PhoneMetadata defa
var validNumberPattern = generalDesc.GetNationalNumberPattern();
if ((!validNumberPattern.IsMatchAll(normalizedNumber) &&
validNumberPattern.IsMatchAll(potentialNationalNumberString)) ||
TestNumberLength(normalizedNumber.Length, defaultRegionMetadata) == ValidationResult.TOO_LONG)
TestNumberLength(fullNumber.Length, defaultRegionMetadata) == ValidationResult.TOO_LONG)
{
nationalNumber.Append(potentialNationalNumberString);
if (keepRawInput)
Expand Down Expand Up @@ -2529,7 +2539,11 @@ static string MaybeStripExtension(StringBuilder number, string numberString)
var m = ExtnPattern().Match(numberString);
// If we find a potential extension, and the number preceding this is a viable number, we assume
// it is an extension.
#if NET7_0_OR_GREATER
if (m.Success && IsViablePhoneNumberSpan(numberString.AsSpan(0, m.Index)))
#else
if (m.Success && IsViablePhoneNumber(numberString.Substring(0, m.Index)))
#endif
{
// The numbers are captured into groups in the regular expression.
for (int i = 1, length = m.Groups.Count; i < length; i++)
Expand Down
17 changes: 17 additions & 0 deletions csharp/PhoneNumbers/PhoneNumberUtil.net.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,15 @@ private static void NormalizeHelper(ref Span<char> span,
}
}

private static string NormalizeHelper(string number, Func<char, char> normalizationReplacements, bool removeNonMatches)
{
if (number.Length == 0) return string.Empty;
Span<char> result = stackalloc char[number.Length];
var resultLength = 0;
NormalizeHelper(ref result, ref resultLength, number, normalizationReplacements, removeNonMatches);
Comment on lines +611 to +614
return new string(result.Slice(0, resultLength));
}

private static bool IsValidAlphaPhone(string number)
{
for (int alpha = 0, i = 0; i < number.Length; i++)
Expand Down Expand Up @@ -750,6 +759,14 @@ private void PrefixNumberWithCountryCallingCode(ref Span<char> span,
return;
}
}

#if NET7_0_OR_GREATER
internal static bool IsViablePhoneNumberSpan(ReadOnlySpan<char> number)
{
if (number.Length < MIN_LENGTH_FOR_NSN) return false;
return ValidPhoneNumber().IsMatch(number);
}
#endif
}
}
#endif
Loading