Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Arabic and Persian numerals #332

Merged
merged 1 commit into from
Jan 7, 2020
Merged
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
3 changes: 1 addition & 2 deletions PhoneNumberKit/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ struct PhoneNumberPatterns {
static let fgPattern = "\\$FG"
static let npPattern = "\\$NP"

static let allNormalizationMappings = ["0":"0", "1":"1", "2":"2", "3":"3", "4":"4", "5":"5", "6":"6", "7":"7", "8":"8", "9":"9", "*": "*", "#": "#", ",": ",", ";": ";"]

static let allNormalizationMappings = ["0":"0", "1":"1", "2":"2", "3":"3", "4":"4", "5":"5", "6":"6", "7":"7", "8":"8", "9":"9", "٠":"0", "١":"1", "٢":"2", "٣":"3", "٤":"4", "٥":"5", "٦":"6", "٧":"7", "٨":"8", "٩":"9", "۰":"0", "۱":"1", "۲":"2", "۳":"3", "۴":"4", "۵":"5", "۶":"6", "۷":"7", "۸":"8", "۹":"9", "*":"*", "#":"#", ",":",", ";":";"]
static let capturingDigitPattern = "([0-90-9٠-٩۰-۹])"

static let extnPattern = "(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xxX##~~;]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)$"
Expand Down
3 changes: 2 additions & 1 deletion PhoneNumberKit/ParseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ final class ParseManager {

let match = try regexManager.phoneDataDetectorMatch(numberString)
let matchedNumber = nationalNumber.substring(with: match.range)
nationalNumber = matchedNumber
// Replace Arabic and Persian numerals and let the rest unchanged
nationalNumber = regexManager.stringByReplacingOccurrences(matchedNumber, map: PhoneNumberPatterns.allNormalizationMappings, keepUnmapped: true)

// Strip and extract extension (3)
var numberExtension: String?
Expand Down
4 changes: 3 additions & 1 deletion PhoneNumberKit/RegexManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,15 @@ final class RegexManager {
}
}

func stringByReplacingOccurrences(_ string: String, map: [String: String]) -> String {
func stringByReplacingOccurrences(_ string: String, map: [String: String], keepUnmapped: Bool = false) -> String {
var targetString = String()
for i in 0..<string.count {
let oneChar = string[string.index(string.startIndex, offsetBy: i)]
let keyString = String(oneChar).uppercased()
if let mappedValue = map[keyString] {
targetString.append(mappedValue)
} else if keepUnmapped {
targetString.append(keyString)
}
}
return targetString
Expand Down
124 changes: 124 additions & 0 deletions PhoneNumberKitTests/PartialFormatterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,130 @@ class PartialFormatterTests: XCTestCase {
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "07739 555555,9,1;2;5")
}

// +٩٧١٥٠٠٥٠٠٥٥٠ (+971500500550)
func testAENumberWithHinduArabicNumerals() {
let partialFormatter = PartialFormatter(phoneNumberKit: phoneNumberKit, defaultRegion: "AE")
var testNumber = "+"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+")
testNumber = "+٩"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9")
testNumber = "+٩٧"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9 7")
testNumber = "+٩٧١"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971")
testNumber = "+٩٧١٥"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 5")
testNumber = "+٩٧١٥٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50")
testNumber = "+٩٧١٥٠٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 0")
testNumber = "+٩٧١٥٠٠٥"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 05")
testNumber = "+٩٧١٥٠٠٥٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050")
testNumber = "+٩٧١٥٠٠٥٠٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0")
testNumber = "+٩٧١٥٠٠٥٠٠٥"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 05")
testNumber = "+٩٧١٥٠٠٥٠٠٥٥"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 055")
testNumber = "+٩٧١٥٠٠٥٠٠٥٥٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0550")
}

// +٩٧١5٠٠5٠٠55٠ (+971500500550)
func testAENumberWithMixedHinduArabicNumerals() {
let partialFormatter = PartialFormatter(phoneNumberKit: phoneNumberKit, defaultRegion: "AE")
var testNumber = "+"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+")
testNumber = "+٩"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9")
testNumber = "+٩٧"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9 7")
testNumber = "+٩٧١"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971")
testNumber = "+٩٧١5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 5")
testNumber = "+٩٧١5٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50")
testNumber = "+٩٧١5٠٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 0")
testNumber = "+٩٧١5٠٠5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 05")
testNumber = "+٩٧١5٠٠5٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050")
testNumber = "+٩٧١5٠٠5٠٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0")
testNumber = "+٩٧١5٠٠5٠٠5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 05")
testNumber = "+٩٧١5٠٠5٠٠55"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 055")
testNumber = "+٩٧١5٠٠5٠٠55٠"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0550")
}

// +۹۷۱۵۰۰۵۰۰۵۵۰ (+971500500550)
func testAENumberWithEasternArabicNumerals() {
let partialFormatter = PartialFormatter(phoneNumberKit: phoneNumberKit, defaultRegion: "AE")
var testNumber = "+"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+")
testNumber = "+۹"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9")
testNumber = "+۹۷"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9 7")
testNumber = "+۹۷١"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971")
testNumber = "+۹۷۱۵"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 5")
testNumber = "+۹۷۱۵۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50")
testNumber = "+۹۷۱۵۰۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 0")
testNumber = "+۹۷۱۵۰۰۵"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 05")
testNumber = "+۹۷۱۵۰۰۵۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050")
testNumber = "+۹۷۱۵۰۰۵۰۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0")
testNumber = "+۹۷۱۵۰۰۵۰۰۵"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 05")
testNumber = "+۹۷۱۵۰۰۵۰۰۵۵"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 055")
testNumber = "+۹۷۱۵۰۰۵۰۰۵۵۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0550")
}

// +۹۷۱5۰۰5۰۰55۰ (+971500500550)
func testAENumberWithMixedEasternArabicNumerals() {
let partialFormatter = PartialFormatter(phoneNumberKit: phoneNumberKit, defaultRegion: "AE")
var testNumber = "+"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+")
testNumber = "+۹"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9")
testNumber = "+۹۷"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+9 7")
testNumber = "+۹۷١"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971")
testNumber = "+۹۷۱5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 5")
testNumber = "+۹۷۱5۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50")
testNumber = "+۹۷۱5۰۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 0")
testNumber = "+۹۷۱5۰۰5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 05")
testNumber = "+۹۷۱5۰۰5۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050")
testNumber = "+۹۷۱5۰۰5۰۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0")
testNumber = "+۹۷۱5۰۰5۰۰5"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 05")
testNumber = "+۹۷۱5۰۰5۰۰55"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 055")
testNumber = "+۹۷۱5۰۰5۰۰55۰"
XCTAssertEqual(partialFormatter.formatPartial(testNumber), "+971 50 050 0550")
}

func testWithPrefixDisabled() {
let partialFormatter = PartialFormatter(phoneNumberKit: phoneNumberKit, defaultRegion: "CZ")
partialFormatter.withPrefix = false
Expand Down
60 changes: 60 additions & 0 deletions PhoneNumberKitTests/PhoneNumberKitParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,66 @@ class PhoneNumberKitParsingTests: XCTestCase {
XCTAssertEqual(number.type, PhoneNumberType.mobile)
}

func testAENumberWithHinduArabicNumerals() {
do {
let phoneNumber1 = try phoneNumberKit.parse("+٩٧١٥٠٠٥٠٠٥٥٠", withRegion: "AE")
XCTAssertNotNil(phoneNumber1)
let phoneNumberInternationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .international)
XCTAssertTrue(phoneNumberInternationalFormat1 == "+971 50 050 0550")
let phoneNumberNationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .national)
XCTAssertTrue(phoneNumberNationalFormat1 == "050 050 0550")
let phoneNumberE164Format1 = self.phoneNumberKit.format(phoneNumber1, toType: .e164)
XCTAssertTrue(phoneNumberE164Format1 == "+971500500550")
} catch {
XCTFail()
}
}

func testAENumberWithMixedHinduArabicNumerals() {
do {
let phoneNumber1 = try phoneNumberKit.parse("+٩٧١5٠٠5٠٠55٠", withRegion: "AE")
XCTAssertNotNil(phoneNumber1)
let phoneNumberInternationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .international)
XCTAssertTrue(phoneNumberInternationalFormat1 == "+971 50 050 0550")
let phoneNumberNationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .national)
XCTAssertTrue(phoneNumberNationalFormat1 == "050 050 0550")
let phoneNumberE164Format1 = self.phoneNumberKit.format(phoneNumber1, toType: .e164)
XCTAssertTrue(phoneNumberE164Format1 == "+971500500550")
} catch {
XCTFail()
}
}

func testAENumberWithEasternArabicNumerals() {
do {
let phoneNumber1 = try phoneNumberKit.parse("+۹۷۱۵۰۰۵۰۰۵۵۰", withRegion: "AE")
XCTAssertNotNil(phoneNumber1)
let phoneNumberInternationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .international)
XCTAssertTrue(phoneNumberInternationalFormat1 == "+971 50 050 0550")
let phoneNumberNationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .national)
XCTAssertTrue(phoneNumberNationalFormat1 == "050 050 0550")
let phoneNumberE164Format1 = self.phoneNumberKit.format(phoneNumber1, toType: .e164)
XCTAssertTrue(phoneNumberE164Format1 == "+971500500550")
} catch {
XCTFail()
}
}

func testAENumberWithMixedEasternArabicNumerals() {
do {
let phoneNumber1 = try phoneNumberKit.parse("+۹۷۱5۰۰5۰۰55۰", withRegion: "AE")
XCTAssertNotNil(phoneNumber1)
let phoneNumberInternationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .international)
XCTAssertTrue(phoneNumberInternationalFormat1 == "+971 50 050 0550")
let phoneNumberNationalFormat1 = self.phoneNumberKit.format(phoneNumber1, toType: .national)
XCTAssertTrue(phoneNumberNationalFormat1 == "050 050 0550")
let phoneNumberE164Format1 = self.phoneNumberKit.format(phoneNumber1, toType: .e164)
XCTAssertTrue(phoneNumberE164Format1 == "+971500500550")
} catch {
XCTFail()
}
}

func testPerformanceSimple() {
let numberOfParses = 1000
let startTime = Date()
Expand Down
52 changes: 52 additions & 0 deletions PhoneNumberKitTests/PhoneNumberKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,58 @@ class PhoneNumberKitTests: XCTestCase {
XCTFail()
}
}

func testValidAENumberWithHinduArabicNumerals() {
let testNumber = "+٩٧١٥٠٠٥٠٠٥٥٠"
do {
let phoneNumber = try phoneNumberKit.parse(testNumber, withRegion: "AE")
XCTAssertEqual(self.phoneNumberKit.format(phoneNumber, toType: .e164), "+971500500550")
XCTAssertEqual(phoneNumber.countryCode, 971)
XCTAssertEqual(phoneNumber.nationalNumber, 500500550)
XCTAssertEqual(phoneNumber.leadingZero, false)
} catch {
XCTFail()
}
}

func testValidAENumberWithMixedHinduArabicNumerals() {
let testNumber = "+٩٧١5٠٠5٠٠55٠"
do {
let phoneNumber = try phoneNumberKit.parse(testNumber, withRegion: "AE")
XCTAssertEqual(self.phoneNumberKit.format(phoneNumber, toType: .e164), "+971500500550")
XCTAssertEqual(phoneNumber.countryCode, 971)
XCTAssertEqual(phoneNumber.nationalNumber, 500500550)
XCTAssertEqual(phoneNumber.leadingZero, false)
} catch {
XCTFail()
}
}

func testValidAENumberWithEasternArabicNumerals() {
let testNumber = "+۹۷۱۵۰۰۵۰۰۵۵۰"
do {
let phoneNumber = try phoneNumberKit.parse(testNumber, withRegion: "AE")
XCTAssertEqual(self.phoneNumberKit.format(phoneNumber, toType: .e164), "+971500500550")
XCTAssertEqual(phoneNumber.countryCode, 971)
XCTAssertEqual(phoneNumber.nationalNumber, 500500550)
XCTAssertEqual(phoneNumber.leadingZero, false)
} catch {
XCTFail()
}
}

func testValidAENumberWithMixedEasternArabicNumerals() {
let testNumber = "+۹۷۱5۰۰5۰۰55۰"
do {
let phoneNumber = try phoneNumberKit.parse(testNumber, withRegion: "AE")
XCTAssertEqual(self.phoneNumberKit.format(phoneNumber, toType: .e164), "+971500500550")
XCTAssertEqual(phoneNumber.countryCode, 971)
XCTAssertEqual(phoneNumber.nationalNumber, 500500550)
XCTAssertEqual(phoneNumber.leadingZero, false)
} catch {
XCTFail()
}
}

// Invalid number too short
func testInvalidNumberTooShort() {
Expand Down