Skip to content

Commit

Permalink
Merge pull request #258 from marmelroy/codable_xmljjson
Browse files Browse the repository at this point in the history
Decodable Metadata + Script for metadata generation + Swift Lint + Metadata updates
  • Loading branch information
marmelroy authored Feb 10, 2019
2 parents c523be6 + fa857d8 commit 9fc1071
Show file tree
Hide file tree
Showing 19 changed files with 2,152 additions and 2,043 deletions.
4 changes: 4 additions & 0 deletions PhoneNumberKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
11C2EF391D62BC3200052D44 /* NSRegularExpression+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C2EF381D62BC3200052D44 /* NSRegularExpression+Swift.swift */; };
3417BD6B2210AC4900477EE7 /* MetadataParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3417BD6A2210AC4900477EE7 /* MetadataParsing.swift */; };
3420CF5E1BE8959F00FAE34F /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3420CF5D1BE8959F00FAE34F /* Formatter.swift */; };
3422D9BA1BE6A2D500867D02 /* ParseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3422D9B91BE6A2D500867D02 /* ParseManager.swift */; };
3424186F1BB6E5A000EE70E7 /* PhoneNumberKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 342418641BB6E5A000EE70E7 /* PhoneNumberKit.framework */; };
Expand Down Expand Up @@ -82,6 +83,7 @@

/* Begin PBXFileReference section */
11C2EF381D62BC3200052D44 /* NSRegularExpression+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+Swift.swift"; sourceTree = "<group>"; };
3417BD6A2210AC4900477EE7 /* MetadataParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataParsing.swift; sourceTree = "<group>"; };
3420CF5D1BE8959F00FAE34F /* Formatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = "<group>"; };
3422D9B91BE6A2D500867D02 /* ParseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseManager.swift; sourceTree = "<group>"; };
342418641BB6E5A000EE70E7 /* PhoneNumberKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhoneNumberKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -156,6 +158,7 @@
children = (
346922661BC01DCC0023482F /* MetadataManager.swift */,
342548EF1BE7EED500FBE524 /* MetadataTypes.swift */,
3417BD6A2210AC4900477EE7 /* MetadataParsing.swift */,
);
name = Metadata;
sourceTree = "<group>";
Expand Down Expand Up @@ -465,6 +468,7 @@
346922671BC01DCC0023482F /* MetadataManager.swift in Sources */,
3420CF5E1BE8959F00FAE34F /* Formatter.swift in Sources */,
343B850D1C62A25600918E46 /* TextField.swift in Sources */,
3417BD6B2210AC4900477EE7 /* MetadataParsing.swift in Sources */,
346922691BC023A60023482F /* PhoneNumberKit.swift in Sources */,
343B850C1C62A25600918E46 /* PartialFormatter.swift in Sources */,
3422D9BA1BE6A2D500867D02 /* ParseManager.swift in Sources */,
Expand Down
5 changes: 2 additions & 3 deletions PhoneNumberKit/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public enum PhoneNumberFormat {
case national // 06 89 12 34 56
}


/**
Phone number type enumeration
- fixedLine: Fixed line numbers
Expand Down Expand Up @@ -114,12 +113,12 @@ struct PhoneNumberConstants {

struct PhoneNumberPatterns {
// MARK: Patterns

static let firstGroupPattern = "(\\$\\d)"
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"]

static let capturingDigitPattern = "([0-90-9٠-٩۰-۹])"

Expand Down
38 changes: 16 additions & 22 deletions PhoneNumberKit/Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,19 @@
import Foundation

final class Formatter {

weak var regexManager: RegexManager?

init(phoneNumberKit: PhoneNumberKit) {
self.regexManager = phoneNumberKit.regexManager
}

init(regexManager: RegexManager) {
self.regexManager = regexManager
}


// MARK: Formatting functions

/// Formats phone numbers for display
///
/// - Parameters:
Expand Down Expand Up @@ -51,14 +50,13 @@ final class Formatter {
if let extns = numberExtension {
if let preferredExtnPrefix = regionMetadata.preferredExtnPrefix {
return "\(preferredExtnPrefix)\(extns)"
}
else {
} else {
return "\(PhoneNumberConstants.defaultExtnPrefix)\(extns)"
}
}
return nil
}

/// Formats national number for display
///
/// - Parameters:
Expand All @@ -75,14 +73,13 @@ final class Formatter {
if (regexManager.stringPositionByRegex(leadingDigitPattern, string: String(nationalNumber)) == 0) {
if (regexManager.matchesEntirely(format.pattern, string: String(nationalNumber))) {
selectedFormat = format
break;
break
}
}
}
else {
} else {
if (regexManager.matchesEntirely(format.pattern, string: String(nationalNumber))) {
selectedFormat = format
break;
break
}
}
}
Expand All @@ -96,35 +93,32 @@ final class Formatter {
prefixFormattingRule = regexManager.replaceStringByRegex(PhoneNumberPatterns.npPattern, string: nationalPrefixFormattingRule, template: nationalPrefix)
prefixFormattingRule = regexManager.replaceStringByRegex(PhoneNumberPatterns.fgPattern, string: prefixFormattingRule, template:"\\$1")
}
if formatType == PhoneNumberFormat.national && regexManager.hasValue(prefixFormattingRule){
if formatType == PhoneNumberFormat.national && regexManager.hasValue(prefixFormattingRule) {
let replacePattern = regexManager.replaceFirstStringByRegex(PhoneNumberPatterns.firstGroupPattern, string: numberFormatRule, templateString: prefixFormattingRule)
formattedNationalNumber = regexManager.replaceStringByRegex(pattern, string: nationalNumber, template: replacePattern)
}
else {
} else {
formattedNationalNumber = regexManager.replaceStringByRegex(pattern, string: nationalNumber, template: numberFormatRule)
}
return formattedNationalNumber
}
else {
} else {
return nationalNumber
}
}

}

public extension PhoneNumber {

/**
Adjust national number for display by adding leading zero if needed. Used for basic formatting functions.
- Returns: A string representing the adjusted national number.
*/
public func adjustedNationalNumber() -> String {
if self.leadingZero == true {
return "0" + String(nationalNumber)
}
else {
} else {
return String(nationalNumber)
}
}

}
19 changes: 4 additions & 15 deletions PhoneNumberKit/MetadataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ final class MetadataManager {
territoriesByCountry.removeAll()
}


/// Populates the metadata from a metadataCallback.
///
/// - Parameter metadataCallback: a closure that returns metadata as JSON Data.
Expand All @@ -48,20 +47,11 @@ final class MetadataManager {
var territoryArray = [MetadataTerritory]()
do {
let jsonData: Data? = try metadataCallback()
if let jsonData = jsonData,
let jsonObjects = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary,
let metadataDict = jsonObjects["phoneNumberMetadata"] as? NSDictionary,
let metadataTerritories = metadataDict["territories"] as? NSDictionary ,
let metadataTerritoryArray = metadataTerritories["territory"] as? NSArray {
metadataTerritoryArray.forEach({
if let territoryDict = $0 as? NSDictionary {
let parsedTerritory = MetadataTerritory(jsondDict: territoryDict)
territoryArray.append(parsedTerritory)
}
})
let jsonDecoder = JSONDecoder()
if let jsonData = jsonData, let metadata: PhoneNumberMetadata = try? jsonDecoder.decode(PhoneNumberMetadata.self, from: jsonData) {
territoryArray = metadata.territories
}
}
catch {}
} catch {}
return territoryArray
}

Expand Down Expand Up @@ -94,5 +84,4 @@ final class MetadataManager {
return mainTerritoryByCode[code]
}


}
162 changes: 162 additions & 0 deletions PhoneNumberKit/MetadataParsing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//
// MetadataParsing.swift
// PhoneNumberKit
//
// Created by Roy Marmelstein on 2019-02-10.
// Copyright © 2019 Roy Marmelstein. All rights reserved.
//

import Foundation

// MARK: - MetadataTerritory

extension MetadataTerritory {

enum CodingKeys: String, CodingKey {
case codeID = "id"
case countryCode
case internationalPrefix
case mainCountryForCode
case nationalPrefix
case nationalPrefixFormattingRule
case nationalPrefixForParsing
case nationalPrefixTransformRule
case preferredExtnPrefix
case emergency
case fixedLine
case generalDesc
case mobile
case pager
case personalNumber
case premiumRate
case sharedCost
case tollFree
case voicemail
case voip
case uan
case numberFormats = "numberFormat"
case leadingDigits
case availableFormats
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

// Custom parsing logic
codeID = try container.decode(String.self, forKey: .codeID)
let code = try! container.decode(String.self, forKey: .countryCode)
countryCode = UInt64(code)!
mainCountryForCode = container.decodeBoolString(forKey: .mainCountryForCode)
let possibleNationalPrefixForParsing: String? = try? container.decode(String.self, forKey: .nationalPrefixForParsing)
let possibleNationalPrefix: String? = try? container.decode(String.self, forKey: .nationalPrefix)
nationalPrefix = possibleNationalPrefix
nationalPrefixForParsing = (possibleNationalPrefixForParsing == nil && possibleNationalPrefix != nil) ? nationalPrefix : possibleNationalPrefixForParsing
nationalPrefixFormattingRule = try? container.decode(String.self, forKey: .nationalPrefixFormattingRule)
let availableFormats = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: .availableFormats)
let temporaryFormatList: [MetadataPhoneNumberFormat] = availableFormats?.decodeArrayOrObject(forKey: .numberFormats) ?? [MetadataPhoneNumberFormat]()
numberFormats = temporaryFormatList.withDefaultNationalPrefixFormattingRule(nationalPrefixFormattingRule)

// Default parsing logic
internationalPrefix = try? container.decode(String.self, forKey: .internationalPrefix)
nationalPrefixTransformRule = try? container.decode(String.self, forKey: .nationalPrefixTransformRule)
preferredExtnPrefix = try? container.decode(String.self, forKey: .preferredExtnPrefix)
emergency = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .emergency)
fixedLine = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .fixedLine)
generalDesc = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .generalDesc)
mobile = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .mobile)
pager = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .pager)
personalNumber = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .personalNumber)
premiumRate = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .premiumRate)
sharedCost = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .sharedCost)
tollFree = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .tollFree)
voicemail = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .voicemail)
voip = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .voip)
uan = try? container.decode(MetadataPhoneNumberDesc.self, forKey: .uan)
leadingDigits = try? container.decode(String.self, forKey: .leadingDigits)
}
}

// MARK: - MetadataPhoneNumberFormat

extension MetadataPhoneNumberFormat {
enum CodingKeys: String, CodingKey {
case pattern
case format
case intlFormat
case leadingDigitsPatterns = "leadingDigits"
case nationalPrefixFormattingRule
case nationalPrefixOptionalWhenFormatting
case domesticCarrierCodeFormattingRule = "carrierCodeFormattingRule"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

// Custom parsing logic
leadingDigitsPatterns = container.decodeArrayOrObject(forKey: .leadingDigitsPatterns)
nationalPrefixOptionalWhenFormatting = container.decodeBoolString(forKey: .nationalPrefixOptionalWhenFormatting)

// Default parsing logic
pattern = try? container.decode(String.self, forKey: .pattern)
format = try? container.decode(String.self, forKey: .format)
intlFormat = try? container.decode(String.self, forKey: .intlFormat)
nationalPrefixFormattingRule = try? container.decode(String.self, forKey: .nationalPrefixFormattingRule)
domesticCarrierCodeFormattingRule = try? container.decode(String.self, forKey: .domesticCarrierCodeFormattingRule)
}
}

// MARK: - PhoneNumberMetadata

extension PhoneNumberMetadata {
enum CodingKeys: String, CodingKey {
case phoneNumberMetadata
case territories
case territory
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let metadataObject = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .phoneNumberMetadata)
let territoryObject = try metadataObject.nestedContainer(keyedBy: CodingKeys.self, forKey: .territories)
territories = try territoryObject.decode([MetadataTerritory].self, forKey: .territory)
}
}

// MARK: - Parsing helpers

private extension KeyedDecodingContainer where K : CodingKey {
/// Decodes a string to a boolean. Returns false if empty.
///
/// - Parameter key: Coding key to decode
func decodeBoolString(forKey key: KeyedDecodingContainer<K>.Key) -> Bool {
guard let value: String = try? self.decode(String.self, forKey: key) else {
return false
}
return Bool(value) ?? false
}

/// Decodes either a single object or an array into an array. Returns an empty array if empty.
///
/// - Parameter key: Coding key to decode
func decodeArrayOrObject<T: Decodable>(forKey key: KeyedDecodingContainer<K>.Key) -> [T] {
guard let array: [T] = try? self.decode([T].self, forKey: key) else {
guard let object: T = try? self.decode(T.self, forKey: key) else {
return [T]()
}
return [object]
}
return array
}
}

private extension Collection where Element == MetadataPhoneNumberFormat {
func withDefaultNationalPrefixFormattingRule(_ nationalPrefixFormattingRule: String?) -> [Element] {
return self.map { format -> MetadataPhoneNumberFormat in
var modifiedFormat = format
if modifiedFormat.nationalPrefixFormattingRule == nil {
modifiedFormat.nationalPrefixFormattingRule = nationalPrefixFormattingRule
}
return modifiedFormat
}
}
}
Loading

0 comments on commit 9fc1071

Please sign in to comment.