759 lines
25 KiB
Swift
759 lines
25 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import libPhoneNumber_iOS
|
|
import SignalCoreKit
|
|
|
|
public class PhoneNumberUtilSwiftValues {
|
|
fileprivate let nbPhoneNumberUtil = NBPhoneNumberUtil()
|
|
fileprivate let countryCodesFromCallingCodeCache = AtomicDictionary<String, [String]>([:], lock: .init())
|
|
fileprivate let parsedPhoneNumberCache = LRUCache<String, NBPhoneNumber>(
|
|
maxSize: 256,
|
|
nseMaxSize: 0,
|
|
shouldEvacuateInBackground: false
|
|
)
|
|
fileprivate let nationalPrefixTransformRuleCache = AtomicDictionary<String, String?>([:], lock: .init())
|
|
fileprivate let phoneNumberCountryCodeCache = AtomicDictionary<String, String?>([:], lock: .init())
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension PhoneNumberUtil {
|
|
// country code -> calling code
|
|
public func callingCode(fromCountryCode countryCode: String) -> String? {
|
|
guard let countryCode = countryCode.nilIfEmpty else {
|
|
return "+0"
|
|
}
|
|
|
|
if countryCode == "AQ" {
|
|
// Antarctica
|
|
return "+672"
|
|
} else if countryCode == "BV" {
|
|
// Bouvet Island
|
|
return "+55"
|
|
} else if countryCode == "IC" {
|
|
// Canary Islands
|
|
return "+34"
|
|
} else if countryCode == "EA" {
|
|
// Ceuta & Melilla
|
|
return "+34"
|
|
} else if countryCode == "CP" {
|
|
// Clipperton Island
|
|
//
|
|
// This country code should be filtered - it does not appear to have a calling code.
|
|
return nil
|
|
} else if countryCode == "DG" {
|
|
// Diego Garcia
|
|
return "+246"
|
|
} else if countryCode == "TF" {
|
|
// French Southern Territories
|
|
return "+262"
|
|
} else if countryCode == "HM" {
|
|
// Heard & McDonald Islands
|
|
return "+672"
|
|
} else if countryCode == "XK" {
|
|
// Kosovo
|
|
return "+383"
|
|
} else if countryCode == "PN" {
|
|
// Pitcairn Islands
|
|
return "+64"
|
|
} else if countryCode == "GS" {
|
|
// So. Georgia & So. Sandwich Isl.
|
|
return "+500"
|
|
} else if countryCode == "UM" {
|
|
// U.S. Outlying Islands
|
|
return "+1"
|
|
}
|
|
|
|
let callingCode = getCallingCode(forRegion: countryCode)
|
|
return COUNTRY_CODE_PREFIX + "\(callingCode.intValue)"
|
|
}
|
|
|
|
@objc
|
|
public static func defaultCountryCode() -> String {
|
|
return Locale.current.regionCode ?? "US"
|
|
}
|
|
|
|
// Returns a list of country codes for a calling code in descending
|
|
// order of population.
|
|
public func countryCodes(fromCallingCode callingCode: String) -> [String] {
|
|
guard let callingCode = callingCode.nilIfEmpty else {
|
|
return []
|
|
}
|
|
if let cachedValue = swiftValues.countryCodesFromCallingCodeCache[callingCode] {
|
|
return cachedValue
|
|
}
|
|
let countryCodes: [String] = Self.countryCodesSortedByPopulationDescending.compactMap { (countryCode: String) -> String? in
|
|
let callingCodeForCountryCode: String? = self.callingCode(fromCountryCode: countryCode)
|
|
guard callingCode == callingCodeForCountryCode else {
|
|
return nil
|
|
}
|
|
return countryCode
|
|
}
|
|
swiftValues.countryCodesFromCallingCodeCache[callingCode] = countryCodes
|
|
return countryCodes
|
|
}
|
|
|
|
@objc
|
|
public func format(_ phoneNumber: NBPhoneNumber, numberFormat: NBEPhoneNumberFormat) throws -> String {
|
|
try swiftValues.nbPhoneNumberUtil.format(phoneNumber, numberFormat: numberFormat)
|
|
}
|
|
|
|
@objc
|
|
public func parse(_ numberToParse: String, defaultRegion: String) throws -> NBPhoneNumber {
|
|
let hashKey = "numberToParse:\(numberToParse), defaultRegion:\(defaultRegion)"
|
|
if let cachedValue = swiftValues.parsedPhoneNumberCache[hashKey] {
|
|
return cachedValue
|
|
}
|
|
let result = try swiftValues.nbPhoneNumberUtil.parse(numberToParse, defaultRegion: defaultRegion)
|
|
swiftValues.parsedPhoneNumberCache[hashKey] = result
|
|
return result
|
|
}
|
|
|
|
public func examplePhoneNumber(forCountryCode countryCode: String) -> String? {
|
|
// Signal users are very likely using mobile devices, so prefer that kind of example.
|
|
do {
|
|
func findExamplePhoneNumber() -> NBPhoneNumber? {
|
|
let types: [NBEPhoneNumberType] = [.MOBILE, .FIXED_LINE_OR_MOBILE]
|
|
for type in types {
|
|
let phoneNumber = PhoneNumberUtil.getExampleNumber(
|
|
forType: countryCode,
|
|
type: type,
|
|
nbPhoneNumberUtil: swiftValues.nbPhoneNumberUtil
|
|
)
|
|
if let phoneNumber {
|
|
return phoneNumber
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
guard let nbPhoneNumber = findExamplePhoneNumber() else {
|
|
owsFailDebug("Could not find example phone number for: \(countryCode)")
|
|
return nil
|
|
}
|
|
return try swiftValues.nbPhoneNumberUtil.format(nbPhoneNumber, numberFormat: .E164)
|
|
} catch {
|
|
owsFailDebug("Error: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func isPossibleNumber(_ number: NBPhoneNumber) -> Bool {
|
|
return swiftValues.nbPhoneNumberUtil.isPossibleNumber(number)
|
|
}
|
|
|
|
@objc
|
|
public func getRegionCodeForCountryCode(_ countryCallingCode: NSNumber) -> String {
|
|
return swiftValues.nbPhoneNumberUtil.getRegionCode(forCountryCode: countryCallingCode)
|
|
}
|
|
|
|
@objc
|
|
public func isValidNumber(_ number: NBPhoneNumber) -> Bool {
|
|
return swiftValues.nbPhoneNumberUtil.isValidNumber(number)
|
|
}
|
|
|
|
public func getCallingCode(forRegion regionCode: String) -> NSNumber {
|
|
return swiftValues.nbPhoneNumberUtil.getCountryCode(forRegion: regionCode)
|
|
}
|
|
|
|
@objc
|
|
public func nationalNumber(for phoneNumber: PhoneNumber) -> String {
|
|
return swiftValues.nbPhoneNumberUtil.getNationalSignificantNumber(phoneNumber.nbPhoneNumber)
|
|
}
|
|
|
|
@objc
|
|
public func formattedNationalNumber(for phoneNumber: PhoneNumber) -> String? {
|
|
return try? format(phoneNumber.nbPhoneNumber, numberFormat: .NATIONAL)
|
|
}
|
|
|
|
/// Returns the most likely country code for a calling code based on population.
|
|
/// If no country codes are found, returns the empty string.
|
|
@objc
|
|
public func probableCountryCode(forCallingCode callingCode: String) -> String {
|
|
return countryCodes(fromCallingCode: callingCode).first ?? ""
|
|
}
|
|
|
|
private class func does(_ string: String, matchQuery query: String) -> Bool {
|
|
let searchOptions: String.CompareOptions = [.caseInsensitive, .anchored]
|
|
|
|
let stringTokens = string.components(separatedBy: .whitespaces)
|
|
let queryTokens = query.components(separatedBy: .whitespaces)
|
|
|
|
return queryTokens.allSatisfy { queryToken in
|
|
if queryToken.isEmpty {
|
|
return true
|
|
}
|
|
return stringTokens.contains { stringToken in
|
|
stringToken.range(of: queryToken, options: searchOptions) != nil
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get country codes from a search term.
|
|
@objc
|
|
public func countryCodes(forSearchTerm searchTerm: String?) -> [String] {
|
|
let cleanedSearch = (searchTerm ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
let codes = NSLocale.isoCountryCodes.filter { countryCode in
|
|
guard
|
|
let callingCode = callingCode(fromCountryCode: countryCode),
|
|
!callingCode.isEmpty,
|
|
callingCode != "+0"
|
|
else {
|
|
return false
|
|
}
|
|
let countryName = Self.countryName(fromCountryCode: countryCode)
|
|
return (
|
|
cleanedSearch.isEmpty ||
|
|
Self.does(countryName, matchQuery: cleanedSearch) ||
|
|
Self.does(countryCode, matchQuery: cleanedSearch) ||
|
|
callingCode.contains(cleanedSearch)
|
|
)
|
|
}
|
|
|
|
return codes.sorted { lhs, rhs in
|
|
let lhsCountry = Self.countryName(fromCountryCode: lhs)
|
|
let rhsCountry = Self.countryName(fromCountryCode: rhs)
|
|
return lhsCountry.localizedCaseInsensitiveCompare(rhsCountry) == .orderedAscending
|
|
}
|
|
}
|
|
|
|
/// Convert country code to country name.
|
|
@objc
|
|
public class func countryName(fromCountryCode countryCode: String) -> String {
|
|
lazy var unknownValue = OWSLocalizedString(
|
|
"UNKNOWN_VALUE",
|
|
comment: "Indicates an unknown or unrecognizable value."
|
|
)
|
|
|
|
if countryCode.isEmpty {
|
|
return unknownValue
|
|
}
|
|
|
|
return Locale.current.localizedString(forRegionCode: countryCode)?.nilIfEmpty ?? unknownValue
|
|
}
|
|
|
|
private func parsePhoneNumber(_ numberToParse: String, regionCode: String) -> PhoneNumber? {
|
|
do {
|
|
let phoneNumber = try parse(numberToParse, defaultRegion: regionCode)
|
|
guard isPossibleNumber(phoneNumber) else {
|
|
return nil
|
|
}
|
|
let phoneNumberE164 = try format(phoneNumber, numberFormat: .E164)
|
|
return PhoneNumber(nbPhoneNumber: phoneNumber, e164: phoneNumberE164)
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
@objc
|
|
public func parsePhoneNumber(userSpecifiedText: String) -> PhoneNumber? {
|
|
if userSpecifiedText.isEmpty {
|
|
return nil
|
|
}
|
|
let sanitizedText = (userSpecifiedText as NSString).filterAsE164()
|
|
return parsePhoneNumber(sanitizedText, regionCode: Self.defaultCountryCode())
|
|
}
|
|
|
|
/// `text` may omit the calling code or duplicate the value in `callingCode`.
|
|
public func parsePhoneNumber(userSpecifiedText: String, callingCode: String) -> PhoneNumber? {
|
|
if userSpecifiedText.isEmpty {
|
|
return nil
|
|
}
|
|
let regionCode = getRegionCodeForCountryCode(NSNumber(value: (callingCode as NSString).integerValue))
|
|
if regionCode == NB_UNKNOWN_REGION {
|
|
return parsePhoneNumber(userSpecifiedText: callingCode.appending(userSpecifiedText))
|
|
}
|
|
var sanitizedText = (userSpecifiedText as NSString).filterAsE164()
|
|
if sanitizedText.hasPrefix("+") {
|
|
sanitizedText = String(sanitizedText.dropFirst())
|
|
}
|
|
return parsePhoneNumber(sanitizedText, regionCode: regionCode)
|
|
}
|
|
|
|
@objc
|
|
public func parseE164(_ phoneNumberString: String) -> PhoneNumber? {
|
|
guard let phoneNumber = E164(phoneNumberString) else {
|
|
return nil
|
|
}
|
|
return parseE164(phoneNumber)
|
|
}
|
|
|
|
public func parseE164(_ phoneNumber: E164) -> PhoneNumber? {
|
|
return parsePhoneNumber(phoneNumber.stringValue, regionCode: "ZZ")
|
|
}
|
|
|
|
@objc
|
|
public func parsePhoneNumbers(userSpecifiedText: String, localPhoneNumber: String?) -> [PhoneNumber] {
|
|
var results = parsePhoneNumbers(normalizedText: userSpecifiedText, localPhoneNumber: localPhoneNumber)
|
|
|
|
// A handful of countries (Mexico, Argentina, etc.) require a "national"
|
|
// prefix after their country calling code.
|
|
//
|
|
// It's a bit hacky, but we reconstruct these national prefixes from
|
|
// libPhoneNumber's parsing logic. It's okay if we botch this a little. The
|
|
// risk is that we end up with some misformatted numbers with extra
|
|
// non-numeric regex syntax. These erroneously parsed numbers will never be
|
|
// presented to the user, since they'll never survive the contacts
|
|
// intersection.
|
|
//
|
|
// Try to apply a "national prefix" using the phone's region and using the
|
|
// region that corresponds to the calling code for the local phone number.
|
|
let countryCodes: [String] = [
|
|
Self.defaultCountryCode(),
|
|
localPhoneNumber.flatMap { countryCode(for: $0) },
|
|
].compacted().removingDuplicates(uniquingElementsBy: { $0 })
|
|
|
|
for countryCode in countryCodes {
|
|
guard let transformRule = nationalPrefixTransformRule(countryCode: countryCode) else {
|
|
continue
|
|
}
|
|
guard transformRule.contains("$1") else {
|
|
continue
|
|
}
|
|
let normalizedText = transformRule.replacingOccurrences(of: "$1", with: userSpecifiedText)
|
|
guard !normalizedText.contains("$") else {
|
|
continue
|
|
}
|
|
results.append(contentsOf: parsePhoneNumbers(
|
|
normalizedText: normalizedText,
|
|
localPhoneNumber: localPhoneNumber
|
|
))
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
/// This will try to parse the input text as a phone number using the
|
|
/// default region and the country code for this client's phone number.
|
|
///
|
|
/// Order matters; better results will appear first.
|
|
func parsePhoneNumbers(normalizedText: String, localPhoneNumber: String?) -> [PhoneNumber] {
|
|
guard let text = (normalizedText as NSString).filterAsE164().nilIfEmpty else {
|
|
return []
|
|
}
|
|
|
|
var results = [PhoneNumber]()
|
|
var phoneNumbers = Set<String>()
|
|
|
|
func tryParsing(_ text: String, countryCode: String) {
|
|
guard let phoneNumber = parsePhoneNumber(text, regionCode: countryCode) else {
|
|
return
|
|
}
|
|
guard phoneNumbers.insert(phoneNumber.toE164()).inserted else {
|
|
return
|
|
}
|
|
results.append(phoneNumber)
|
|
}
|
|
|
|
let defaultCountryCode = Self.defaultCountryCode()
|
|
|
|
tryParsing(text, countryCode: defaultCountryCode)
|
|
|
|
if text.hasPrefix("+") {
|
|
// If the text starts with "+", don't try prepending
|
|
// anything else.
|
|
return results
|
|
}
|
|
|
|
// Try just adding "+" and parsing it.
|
|
tryParsing("+" + text, countryCode: defaultCountryCode)
|
|
|
|
// Order matters; better results should appear first so prefer
|
|
// matches with the same country code as this client's phone number.
|
|
guard let localPhoneNumber else {
|
|
owsFailDebug("localPhoneNumber is missing")
|
|
return results
|
|
}
|
|
|
|
// Note that NBPhoneNumber uses "country code" to refer to what we call a
|
|
// "calling code" (i.e. 44 in +44123123). Within SSK we use "country code"
|
|
// (and sometimes "region code") to refer to a country's ISO 2-letter code
|
|
// (ISO 3166-1 alpha-2).
|
|
guard let callingCodeForLocalNumber = parseE164(localPhoneNumber)?.getCallingCode() else {
|
|
owsFailDebug("callingCodeForLocalNumber is missing")
|
|
return results
|
|
}
|
|
|
|
let callingCodePrefix = "+\(callingCodeForLocalNumber)"
|
|
|
|
tryParsing(callingCodePrefix + text, countryCode: defaultCountryCode)
|
|
|
|
// Try to determine what the country code is for the local phone number and
|
|
// also try parsing the phone number using that country code if it differs
|
|
// from the device's region code.
|
|
//
|
|
// For example, a French person living in Italy might have an Italian phone
|
|
// number but use French region/language for their phone. They're likely to
|
|
// have both Italian and French contacts.
|
|
let localCountryCode = probableCountryCode(forCallingCode: callingCodePrefix)
|
|
if localCountryCode != defaultCountryCode {
|
|
tryParsing(callingCodePrefix + text, countryCode: localCountryCode)
|
|
}
|
|
|
|
let phoneNumberWithAreaCodeIfMissing = Self.phoneNumberWithAreaCodeIfMissing(
|
|
normalizedText: text,
|
|
localCallingCode: callingCodeForLocalNumber,
|
|
localPhoneNumber: localPhoneNumber
|
|
)
|
|
if let phoneNumberWithAreaCodeIfMissing {
|
|
tryParsing(phoneNumberWithAreaCodeIfMissing, countryCode: localCountryCode)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
/// Adds the local user's area code to `normalizedText` if it doesn't have its own.
|
|
private static func phoneNumberWithAreaCodeIfMissing(
|
|
normalizedText: String,
|
|
localCallingCode: NSNumber,
|
|
localPhoneNumber: String
|
|
) -> String? {
|
|
switch localCallingCode.intValue {
|
|
case 1:
|
|
return addMissingAreaCode(
|
|
normalizedText: normalizedText,
|
|
localCallingCode: localCallingCode,
|
|
localPhoneNumber: localPhoneNumber,
|
|
missingAreaCodeRegex: Self.missingUSAreaCodeRegex,
|
|
areaCodeRegex: Self.usAreaCodeRegex
|
|
)
|
|
case 55:
|
|
return addMissingAreaCode(
|
|
normalizedText: normalizedText,
|
|
localCallingCode: localCallingCode,
|
|
localPhoneNumber: localPhoneNumber,
|
|
missingAreaCodeRegex: Self.missingBRAreaCodeRegex,
|
|
areaCodeRegex: Self.brAreaCodeRegex
|
|
)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static let missingUSAreaCodeRegex: NSRegularExpression = {
|
|
return try! NSRegularExpression(pattern: #"^(\d{7})$"#)
|
|
}()
|
|
|
|
private static let usAreaCodeRegex: NSRegularExpression = {
|
|
return try! NSRegularExpression(pattern: #"^\+1(\d{3})"#)
|
|
}()
|
|
|
|
private static let missingBRAreaCodeRegex: NSRegularExpression = {
|
|
return try! NSRegularExpression(pattern: #"^(9?\d{8})$"#)
|
|
}()
|
|
|
|
private static let brAreaCodeRegex: NSRegularExpression = {
|
|
return try! NSRegularExpression(pattern: #"^\+55(\d{2})9?\d{8}"#)
|
|
}()
|
|
|
|
private static func addMissingAreaCode(
|
|
normalizedText: String,
|
|
localCallingCode: NSNumber,
|
|
localPhoneNumber: String,
|
|
missingAreaCodeRegex: NSRegularExpression,
|
|
areaCodeRegex: NSRegularExpression
|
|
) -> String? {
|
|
let match = missingAreaCodeRegex.firstMatch(in: normalizedText, range: normalizedText.entireRange)
|
|
if match == nil {
|
|
return nil
|
|
}
|
|
let localAreaCodeNSRange = areaCodeRegex.matches(
|
|
in: localPhoneNumber,
|
|
range: NSRange(localPhoneNumber.startIndex..., in: localPhoneNumber)
|
|
).first?.range(at: 1)
|
|
guard
|
|
let localAreaCodeNSRange,
|
|
let localAreaCodeRange = Range(localAreaCodeNSRange, in: localPhoneNumber)
|
|
else {
|
|
return nil
|
|
}
|
|
return "+\(localCallingCode)\(localPhoneNumber[localAreaCodeRange])\(normalizedText)"
|
|
}
|
|
|
|
private func nationalPrefixTransformRule(countryCode: String) -> String? {
|
|
if let transformRule = swiftValues.nationalPrefixTransformRuleCache[countryCode] {
|
|
return transformRule
|
|
}
|
|
let transformRule: String? = NBMetadataHelper().getMetadataForRegion(countryCode)?.nationalPrefixTransformRule
|
|
swiftValues.nationalPrefixTransformRuleCache[countryCode] = transformRule
|
|
return transformRule
|
|
}
|
|
|
|
private func countryCode(for phoneNumber: String) -> String? {
|
|
if let countryCode = swiftValues.phoneNumberCountryCodeCache[phoneNumber] {
|
|
return countryCode
|
|
}
|
|
let countryCode: String? = {
|
|
guard let callingCode = parseE164(phoneNumber)?.getCallingCode() else {
|
|
return nil
|
|
}
|
|
return probableCountryCode(forCallingCode: "+\(callingCode)").nilIfEmpty
|
|
}()
|
|
swiftValues.phoneNumberCountryCodeCache[phoneNumber] = countryCode
|
|
return countryCode
|
|
}
|
|
|
|
private static let countryCodesSortedByPopulationDescending: [String] = [
|
|
"CN", // 1330044000
|
|
"IN", // 1173108018
|
|
"US", // 310232863
|
|
"ID", // 242968342
|
|
"BR", // 201103330
|
|
"PK", // 184404791
|
|
"BD", // 156118464
|
|
"NG", // 154000000
|
|
"RU", // 140702000
|
|
"JP", // 127288000
|
|
"MX", // 112468855
|
|
"PH", // 99900177
|
|
"VN", // 89571130
|
|
"ET", // 88013491
|
|
"DE", // 81802257
|
|
"EG", // 80471869
|
|
"TR", // 77804122
|
|
"IR", // 76923300
|
|
"CD", // 70916439
|
|
"TH", // 67089500
|
|
"FR", // 64768389
|
|
"GB", // 62348447
|
|
"IT", // 60340328
|
|
"MM", // 53414374
|
|
"ZA", // 49000000
|
|
"KR", // 48422644
|
|
"CO", // 47790000
|
|
"ES", // 46505963
|
|
"UA", // 45415596
|
|
"TZ", // 41892895
|
|
"AR", // 41343201
|
|
"KE", // 40046566
|
|
"PL", // 38500000
|
|
"SD", // 35000000
|
|
"DZ", // 34586184
|
|
"MA", // 33848242
|
|
"CA", // 33679000
|
|
"UG", // 33398682
|
|
"PE", // 29907003
|
|
"IQ", // 29671605
|
|
"AF", // 29121286
|
|
"NP", // 28951852
|
|
"MY", // 28274729
|
|
"UZ", // 27865738
|
|
"VE", // 27223228
|
|
"SA", // 25731776
|
|
"GH", // 24339838
|
|
"YE", // 23495361
|
|
"KP", // 22912177
|
|
"TW", // 22894384
|
|
"SY", // 22198110
|
|
"MZ", // 22061451
|
|
"RO", // 21959278
|
|
"AU", // 21515754
|
|
"LK", // 21513990
|
|
"MG", // 21281844
|
|
"CI", // 21058798
|
|
"CM", // 19294149
|
|
"CL", // 16746491
|
|
"NL", // 16645000
|
|
"BF", // 16241811
|
|
"NE", // 15878271
|
|
"MW", // 15447500
|
|
"KZ", // 15340000
|
|
"EC", // 14790608
|
|
"KH", // 14453680
|
|
"ML", // 13796354
|
|
"GT", // 13550440
|
|
"ZM", // 13460305
|
|
"AO", // 13068161
|
|
"ZW", // 13061000
|
|
"SN", // 12323252
|
|
"CU", // 11423000
|
|
"RW", // 11055976
|
|
"GR", // 11000000
|
|
"PT", // 10676000
|
|
"TN", // 10589025
|
|
"TD", // 10543464
|
|
"CZ", // 10476000
|
|
"BE", // 10403000
|
|
"GN", // 10324025
|
|
"SO", // 10112453
|
|
"HU", // 9982000
|
|
"BO", // 9947418
|
|
"BI", // 9863117
|
|
"SE", // 9828655
|
|
"DO", // 9823821
|
|
"BY", // 9685000
|
|
"HT", // 9648924
|
|
"BJ", // 9056010
|
|
"AZ", // 8303512
|
|
"SS", // 8260490
|
|
"AT", // 8205000
|
|
"HN", // 7989415
|
|
"CH", // 7581000
|
|
"TJ", // 7487489
|
|
"IL", // 7353985
|
|
"RS", // 7344847
|
|
"BG", // 7148785
|
|
"HK", // 6898686
|
|
"TG", // 6587239
|
|
"LY", // 6461454
|
|
"JO", // 6407085
|
|
"PY", // 6375830
|
|
"LA", // 6368162
|
|
"PG", // 6064515
|
|
"SV", // 6052064
|
|
"NI", // 5995928
|
|
"ER", // 5792984
|
|
"KG", // 5776500
|
|
"DK", // 5484000
|
|
"SK", // 5455000
|
|
"SL", // 5245695
|
|
"FI", // 5244000
|
|
"NO", // 5009150
|
|
"AE", // 4975593
|
|
"TM", // 4940916
|
|
"CF", // 4844927
|
|
"SG", // 4701069
|
|
"GE", // 4630000
|
|
"IE", // 4622917
|
|
"BA", // 4590000
|
|
"CR", // 4516220
|
|
"MD", // 4324000
|
|
"HR", // 4284889
|
|
"NZ", // 4252277
|
|
"LB", // 4125247
|
|
"PR", // 3916632
|
|
"PS", // 3800000
|
|
"LR", // 3685076
|
|
"UY", // 3477000
|
|
"PA", // 3410676
|
|
"MR", // 3205060
|
|
"MN", // 3086918
|
|
"CG", // 3039126
|
|
"AL", // 2986952
|
|
"AM", // 2968000
|
|
"OM", // 2967717
|
|
"LT", // 2944459
|
|
"JM", // 2847232
|
|
"KW", // 2789132
|
|
"LV", // 2217969
|
|
"NA", // 2128471
|
|
"MK", // 2062294
|
|
"BW", // 2029307
|
|
"SI", // 2007000
|
|
"LS", // 1919552
|
|
"XK", // 1800000
|
|
"GM", // 1593256
|
|
"GW", // 1565126
|
|
"GA", // 1545255
|
|
"SZ", // 1354051
|
|
"TT", // 1328019
|
|
"MU", // 1294104
|
|
"EE", // 1291170
|
|
"TL", // 1154625
|
|
"CY", // 1102677
|
|
"GQ", // 1014999
|
|
"FJ", // 875983
|
|
"QA", // 840926
|
|
"RE", // 776948
|
|
"KM", // 773407
|
|
"GY", // 748486
|
|
"DJ", // 740528
|
|
"BH", // 738004
|
|
"BT", // 699847
|
|
"ME", // 666730
|
|
"SB", // 559198
|
|
"CV", // 508659
|
|
"LU", // 497538
|
|
"SR", // 492829
|
|
"MO", // 449198
|
|
"GP", // 443000
|
|
"MQ", // 432900
|
|
"MT", // 403000
|
|
"MV", // 395650
|
|
"BN", // 395027
|
|
"BZ", // 314522
|
|
"IS", // 308910
|
|
"BS", // 301790
|
|
"BB", // 285653
|
|
"EH", // 273008
|
|
"PF", // 270485
|
|
"VU", // 221552
|
|
"NC", // 216494
|
|
"GF", // 195506
|
|
"WS", // 192001
|
|
"ST", // 175808
|
|
"LC", // 160922
|
|
"GU", // 159358
|
|
"YT", // 159042
|
|
"CW", // 141766
|
|
"TO", // 122580
|
|
"VI", // 108708
|
|
"GD", // 107818
|
|
"FM", // 107708
|
|
"VC", // 104217
|
|
"KI", // 92533
|
|
"JE", // 90812
|
|
"SC", // 88340
|
|
"AG", // 86754
|
|
"AD", // 84000
|
|
"IM", // 75049
|
|
"DM", // 72813
|
|
"AW", // 71566
|
|
"MH", // 65859
|
|
"BM", // 65365
|
|
"GG", // 65228
|
|
"AS", // 57881
|
|
"GL", // 56375
|
|
"MP", // 53883
|
|
"KN", // 51134
|
|
"FO", // 48228
|
|
"KY", // 44270
|
|
"SX", // 37429
|
|
"MF", // 35925
|
|
"LI", // 35000
|
|
"MC", // 32965
|
|
"SM", // 31477
|
|
"GI", // 27884
|
|
"AX", // 26711
|
|
"VG", // 21730
|
|
"CK", // 21388
|
|
"TC", // 20556
|
|
"PW", // 19907
|
|
"BQ", // 18012
|
|
"WF", // 16025
|
|
"AI", // 13254
|
|
"TV", // 10472
|
|
"NR", // 10065
|
|
"MS", // 9341
|
|
"BL", // 8450
|
|
"SH", // 7460
|
|
"PM", // 7012
|
|
"IO", // 4000
|
|
"FK", // 2638
|
|
"SJ", // 2550
|
|
"NU", // 2166
|
|
"NF", // 1828
|
|
"CX", // 1500
|
|
"TK", // 1466
|
|
"VA", // 921
|
|
"CC", // 628
|
|
"TF", // 140
|
|
"PN", // 46
|
|
"GS", // 30
|
|
"AC", // 0
|
|
"AQ", // 0
|
|
"BV", // 0
|
|
"CP", // 0
|
|
"DG", // 0
|
|
"EA", // 0
|
|
"HM", // 0
|
|
"IC", // 0
|
|
"TA", // 0
|
|
"UM", // 0
|
|
]
|
|
}
|