From 3207eea87bf1297ced28c591f558171c019a6167 Mon Sep 17 00:00:00 2001 From: Eric Fikus Date: Fri, 25 Mar 2016 14:41:16 -0700 Subject: [PATCH] Fixes for PhoneNumber type determination Several fixes for determining the type of a phone number: - Determine which metadata record to use based on the number's region, not the numeric country code. This fixes using the US metadata for all regions using country code 1, for example. - Adjust the order we check the type regular expressions. The fixed and mobile patterns are the most generic so they should be checked last. - Some fixes for regions with leading zeros - Fix regex matchesEntirely - add parentheses to the expression Also update testAllExampleNumbers to check that the expected type is computed for all example numbers in the metadata. --- PhoneNumberKit/PhoneNumber.swift | 2 +- PhoneNumberKit/PhoneNumberKit.swift | 3 + PhoneNumberKit/PhoneNumberParser.swift | 51 ++++++++----- PhoneNumberKit/RegularExpressions.swift | 2 +- .../PhoneNumberKitParsingTests.swift | 76 ++++++++++++++++++- 5 files changed, 107 insertions(+), 27 deletions(-) diff --git a/PhoneNumberKit/PhoneNumber.swift b/PhoneNumberKit/PhoneNumber.swift index 4c8935772..04cb96139 100644 --- a/PhoneNumberKit/PhoneNumber.swift +++ b/PhoneNumberKit/PhoneNumber.swift @@ -27,7 +27,7 @@ public struct PhoneNumber { public var type: PhoneNumberType { get { let parser = PhoneNumberParser() - let type: PhoneNumberType = parser.checkNumberType(String(nationalNumber), countryCode: countryCode) + let type: PhoneNumberType = parser.checkNumberType(self) return type } } diff --git a/PhoneNumberKit/PhoneNumberKit.swift b/PhoneNumberKit/PhoneNumberKit.swift index 0fddc9675..39a8a229b 100644 --- a/PhoneNumberKit/PhoneNumberKit.swift +++ b/PhoneNumberKit/PhoneNumberKit.swift @@ -90,6 +90,9 @@ public class PhoneNumberKit: NSObject { return region.codeID } } + if number.leadingZero && parser.checkNumberType("0" + nationalNumber, metadata: region) != .Unknown { + return region.codeID + } if parser.checkNumberType(nationalNumber, metadata: region) != .Unknown { return region.codeID } diff --git a/PhoneNumberKit/PhoneNumberParser.swift b/PhoneNumberKit/PhoneNumberParser.swift index ac53e374b..0a9b70cb0 100644 --- a/PhoneNumberKit/PhoneNumberParser.swift +++ b/PhoneNumberKit/PhoneNumberParser.swift @@ -112,14 +112,23 @@ class PhoneNumberParser { /** Check number type (e.g +33 612-345-678 to .Mobile). - - Parameter nationalNumber: National number string. - - Parameter countryCode: International country code (e.g 44 for the UK). - - Returns: Country code is UInt64. + - Parameter phoneNumber: The number to check + - Returns: The type of the number */ - func checkNumberType(nationalNumber: String, countryCode: UInt64) -> PhoneNumberType { - guard let metadata = self.metadata.metadataPerCode[countryCode] else { + func checkNumberType(phoneNumber: PhoneNumber) -> PhoneNumberType { + guard let region = PhoneNumberKit().regionCodeForNumber(phoneNumber) else { return .Unknown } + guard let metadata = metadata.fetchMetadataForCountry(region) else { + return .Unknown + } + if phoneNumber.leadingZero { + let type = checkNumberType("0" + String(phoneNumber.nationalNumber), metadata: metadata) + if type != .Unknown { + return type + } + } + let nationalNumber = String(phoneNumber.nationalNumber) return checkNumberType(nationalNumber, metadata: metadata) } @@ -130,19 +139,8 @@ class PhoneNumberParser { if (regex.hasValue(generalNumberDesc.nationalNumberPattern) == false || isNumberMatchingDesc(nationalNumber, numberDesc: generalNumberDesc) == false) { return .Unknown } - if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.fixedLine)) { - if metadata.fixedLine?.nationalNumberPattern == metadata.mobile?.nationalNumberPattern { - return .FixedOrMobile - } - else if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.mobile)) { - return .FixedOrMobile - } - else { - return .FixedLine - } - } - if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.mobile)) { - return .Mobile + if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.pager)) { + return .Pager } if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.premiumRate)) { return .PremiumRate @@ -159,15 +157,26 @@ class PhoneNumberParser { if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.personalNumber)) { return .PersonalNumber } - if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.pager)) { - return .Pager - } if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.uan)) { return .UAN } if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.voicemail)) { return .Voicemail } + if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.fixedLine)) { + if metadata.fixedLine?.nationalNumberPattern == metadata.mobile?.nationalNumberPattern { + return .FixedOrMobile + } + else if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.mobile)) { + return .FixedOrMobile + } + else { + return .FixedLine + } + } + if (isNumberMatchingDesc(nationalNumber, numberDesc: metadata.mobile)) { + return .Mobile + } return .Unknown } diff --git a/PhoneNumberKit/RegularExpressions.swift b/PhoneNumberKit/RegularExpressions.swift index 58d87187f..e4e747e80 100644 --- a/PhoneNumberKit/RegularExpressions.swift +++ b/PhoneNumberKit/RegularExpressions.swift @@ -140,7 +140,7 @@ class RegularExpressions { guard var pattern = pattern else { return false } - pattern = "^\(pattern)$" + pattern = "^(\(pattern))$" return matchesExist(pattern, string: string) } diff --git a/PhoneNumberKitTests/PhoneNumberKitParsingTests.swift b/PhoneNumberKitTests/PhoneNumberKitParsingTests.swift index 229ddd12b..a59fe45ab 100644 --- a/PhoneNumberKitTests/PhoneNumberKitParsingTests.swift +++ b/PhoneNumberKitTests/PhoneNumberKitParsingTests.swift @@ -203,13 +203,32 @@ class PhoneNumberKitParsingTests: XCTestCase { let metaDataArray = PhoneNumberKit().metadata.items.filter{$0.codeID.characters.count == 2} for metadata in metaDataArray { let codeID = metadata.codeID - let metaDataDescriptions = [metadata.generalDesc, metadata.fixedLine, metadata.mobile, metadata.tollFree, metadata.premiumRate, metadata.sharedCost, metadata.voip, metadata.voicemail, metadata.pager, metadata.uan, metadata.emergency] - for desc in metaDataDescriptions { - if desc != nil { - if let exampleNumber = desc?.exampleNumber { + let metadataWithTypes: [(MetadataPhoneNumberDesc?, PhoneNumberType?)] = [ + (metadata.generalDesc, nil), + (metadata.fixedLine, .FixedLine), + (metadata.mobile, .Mobile), + (metadata.tollFree, .TollFree), + (metadata.premiumRate, .PremiumRate), + (metadata.sharedCost, .SharedCost), + (metadata.voip, .VOIP), + (metadata.voicemail, .Voicemail), + (metadata.pager, .Pager), + (metadata.uan, .UAN), + (metadata.emergency, nil), + ] + metadataWithTypes.forEach { record in + if let desc = record.0 { + if let exampleNumber = desc.exampleNumber { do { let phoneNumber = try PhoneNumber(rawNumber: exampleNumber, region: codeID) XCTAssertNotNil(phoneNumber) + if let type = record.1 { + if phoneNumber.type == .FixedOrMobile { + XCTAssert(type == .FixedLine || type == .Mobile) + } else { + XCTAssertEqual(phoneNumber.type, type, "Expected type \(type) for number \(phoneNumber)") + } + } } catch (let e) { XCTFail("Failed to create PhoneNumber for \(exampleNumber): \(e)") } @@ -235,6 +254,55 @@ class PhoneNumberKitParsingTests: XCTestCase { XCTAssertEqual(number.type, PhoneNumberType.TollFree) } + func testBelizeTollFreeType() { + guard let number = try? PhoneNumber(rawNumber: "08001234123", region: "BZ") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.TollFree) + } + + func testItalyFixedLineType() { + guard let number = try? PhoneNumber(rawNumber: "0669812345", region: "IT") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.FixedLine) + } + + func testMaldivesPagerNumber() { + guard let number = try? PhoneNumber(rawNumber: "7812345", region: "MV") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.Pager) + } + + func testZimbabweVoipType() { + guard let number = try? PhoneNumber(rawNumber: "8686123456", region: "ZW") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.VOIP) + + } + + func testAntiguaPagerNumberType() { + guard let number = try? PhoneNumber(rawNumber: "12684061234") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.Pager) + } + + func testFranceMobileNumberType() { + guard let number = try? PhoneNumber(rawNumber: "+33 612-345-678") else { + XCTFail() + return + } + XCTAssertEqual(number.type, PhoneNumberType.Mobile) + } + func testPerformanceSimple() { let numberOfParses = 1000 let startTime = NSDate()