Project import
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..532bd0a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+# libPhoneNumber for iOS (ARC)
+
+* NBPhoneNumber, NBNumberFormat, NBPhoneNumberDesc, NBPhoneMetaData (Metadata classes)
+
+* NBPhoneNumberUtil (from phonenumberutil.js)
+* NBAsYouTypeFormatter
+
+## Install with CocoaPods
+### Use [CocoaPods](http://cocoapods.org/?q=libPhoneNumber-iOS)
+
+## Install without CocoaPods
+### Add source files to your projects from libPhoneNumber
+ - NBPhoneNumberUtil.h, .m
+ - NBAsYouTypeFormatter.h, .m
+
+ - NBNumberFormat.h, .m
+ - NBPhoneNumber.h, .m
+ - NBPhoneNumberDesc.h, .m
+ - NBPhoneNumberDefines.h
+
+ - NBPhoneNumberMetadata.h, .m
+ - NBPhoneNumberMetadataForTesting.h, .m
+
+ - Add "NBPhoneNumberMetadata.plist" and "NBPhoneNumberMetadataForTesting.plist" to bundle resources
+ - Add "CoreTelephony.framework"
+
+ See sample test code from "libPhoneNumber-iOS / libPhoneNumberTests / libPhoneNumberTests.m"
+
+## Sample Usage
+ NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
+
+ NSError *aError = nil;
+ NBPhoneNumber *myNumber = [phoneUtil parse:@"6766077303" defaultRegion:@"AT" error:&aError];
+
+ if (aError == nil) {
+ // Should check error
+ NSLog(@"isValidPhoneNumber ? [%@]", [phoneUtil isValidNumber:myNumber] ? @"YES":@"NO");
+ NSLog(@"E164 : %@", [phoneUtil format:myNumber numberFormat:NBEPhoneNumberFormatE164
+ error:&aError]);
+ NSLog(@"INTERNATIONAL : %@", [phoneUtil format:myNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL
+ error:&aError]);
+ NSLog(@"NATIONAL : %@", [phoneUtil format:myNumber numberFormat:NBEPhoneNumberFormatNATIONAL
+ error:&aError]);
+ NSLog(@"RFC3966 : %@", [phoneUtil format:myNumber numberFormat:NBEPhoneNumberFormatRFC3966
+ error:&aError]);
+ }
+ else {
+ NSLog(@"Error : %@", [aError localizedDescription]);
+ }
+
+ NSLog (@"extractCountryCode [%ld]", [phoneUtil extractCountryCode:@"823213123123"
+ nationalNumber:nil]);
+
+ NSString *res = nil;
+ UInt32 dRes = [phoneUtil extractCountryCode:@"823213123123" nationalNumber:&res];
+
+ NSLog (@"extractCountryCode [%lu] [%@]", dRes, res);
+
+
+### Visit http://code.google.com/p/libphonenumber/ for more information
+#### or zen.isis@gmail.com
+
+## Metadata managing (updating metadata)
+#### Step1. Fetch "metadata.js" and "metadatafortesting.js" from Repositories
+ svn checkout http://libphonenumber.googlecode.com/svn/trunk/ libphonenumber-read-only
+
+#### Step2. Convert Javascript Object to JSON using PHP scripts
+ Output - "PhoneNumberMetaData.json" and "PhoneNumberMetaDataForTesting.json"
+
+#### Step3. Generate binary file from NBPhoneMetaDataGenerator
+ Output - "NBPhoneNumberMetadata.plist" and "NBPhoneNumberMetadataForTesting.plist"
+
+#### Step4. Update exists "NBPhoneNumberMetadata.plist" and "NBPhoneNumberMetadataForTesting.plist" files
+
+## Porting Rules
+* **Remain** javascript libPhonenumber code logic (and comments etc) as possible for maintenance with *.js
diff --git a/libPhoneNumber/NBAsYouTypeFormatter.h b/libPhoneNumber/NBAsYouTypeFormatter.h
new file mode 100644
index 0000000..c994fd5
--- /dev/null
+++ b/libPhoneNumber/NBAsYouTypeFormatter.h
@@ -0,0 +1,21 @@
+//
+// NBAsYouTypeFormatter.h
+// libPhoneNumber
+//
+// Created by ishtar on 13. 2. 25..
+// Copyright (c) 2013년 NHN. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NBAsYouTypeFormatter : NSObject
+
+- (id)initWithRegionCode:(NSString*)regionCode;
+- (id)initWithRegionCodeForTest:(NSString*)regionCode;
+
+- (NSString*)inputDigit:(NSString*)nextChar;
+- (NSString*)inputDigitAndRememberPosition:(NSString*)nextChar;
+- (NSInteger)getRememberedPosition;
+- (void)clear;
+
+@end
diff --git a/libPhoneNumber/NBAsYouTypeFormatter.m b/libPhoneNumber/NBAsYouTypeFormatter.m
new file mode 100644
index 0000000..742199d
--- /dev/null
+++ b/libPhoneNumber/NBAsYouTypeFormatter.m
@@ -0,0 +1,1146 @@
+//
+// NBAsYouTypeFormatter.m
+// libPhoneNumber
+//
+// Created by ishtar on 13. 2. 25..
+// Copyright (c) 2013년 NHN. All rights reserved.
+//
+
+#import "NBAsYouTypeFormatter.h"
+
+#import "NBPhoneNumberUtil.h"
+#import "NBPhoneMetaData.h"
+#import "NBNumberFormat.h"
+
+
+@interface NBAsYouTypeFormatter ()
+
+@property (nonatomic, strong, readwrite) NSString *DIGIT_PLACEHOLDER_;
+@property (nonatomic, assign, readwrite) NSString *SEPARATOR_BEFORE_NATIONAL_NUMBER_;
+@property (nonatomic, strong, readwrite) NSString *currentOutput_, *currentFormattingPattern_;
+@property (nonatomic, strong, readwrite) NSString *defaultCountry_;
+@property (nonatomic, strong, readwrite) NSString *nationalPrefixExtracted_;
+@property (nonatomic, strong, readwrite) NSMutableString *formattingTemplate_, *accruedInput_, *prefixBeforeNationalNumber_, *accruedInputWithoutFormatting_, *nationalNumber_;
+@property (nonatomic, strong, readwrite) NSRegularExpression *DIGIT_PATTERN_, *NATIONAL_PREFIX_SEPARATORS_PATTERN_, *CHARACTER_CLASS_PATTERN_, *STANDALONE_DIGIT_PATTERN_;
+@property (nonatomic, strong, readwrite) NSRegularExpression *ELIGIBLE_FORMAT_PATTERN_;
+@property (nonatomic, assign, readwrite) BOOL ableToFormat_, inputHasFormatting_, isCompleteNumber_, isExpectingCountryCallingCode_, shouldAddSpaceAfterNationalPrefix_;
+@property (nonatomic, strong, readwrite) NBPhoneNumberUtil *phoneUtil_;
+@property (nonatomic, assign, readwrite) NSUInteger lastMatchPosition_, originalPosition_, positionToRemember_;
+@property (nonatomic, assign, readwrite) NSUInteger MIN_LEADING_DIGITS_LENGTH_;
+@property (nonatomic, strong, readwrite) NSMutableArray *possibleFormats_;
+@property (nonatomic, strong, readwrite) NBPhoneMetaData *currentMetaData_, *defaultMetaData_, *EMPTY_METADATA_;
+
+@end
+
+
+@implementation NBAsYouTypeFormatter
+
+- (id)init
+{
+ self = [super init];
+
+ if (self) {
+ /**
+ * @type {i18n.phonenumbers.PhoneNumberUtil}
+ * @private
+ */
+ self.phoneUtil_ = [NBPhoneNumberUtil sharedInstance];
+
+ /**
+ * The digits that have not been entered yet will be represented by a \u2008,
+ * the punctuation space.
+ * @const
+ * @type {string}
+ * @private
+ */
+ self.DIGIT_PLACEHOLDER_ = @"\u2008";
+
+ /**
+ * Character used when appropriate to separate a prefix, such as a long NDD or a
+ * country calling code, from the national number.
+ * @const
+ * @type {string}
+ * @private
+ */
+ self.SEPARATOR_BEFORE_NATIONAL_NUMBER_ = @" ";
+
+ /**
+ * This is the minimum length of national number accrued that is required to
+ * trigger the formatter. The first element of the leadingDigitsPattern of
+ * each numberFormat contains a regular expression that matches up to this
+ * number of digits.
+ * @const
+ * @type {number}
+ * @private
+ */
+ self.MIN_LEADING_DIGITS_LENGTH_ = 3;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ self.currentOutput_ = @"";
+
+ /**
+ * @type {!goog.string.StringBuffer}
+ * @private
+ */
+ self.formattingTemplate_ = [NSMutableString stringWithString:@""];
+
+ NSError *aError = nil;
+
+ /**
+ * @type {RegExp}
+ * @private
+ */
+ self.DIGIT_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:self.DIGIT_PLACEHOLDER_ options:0 error:&aError];
+
+ /**
+ * A set of characters that, if found in a national prefix formatting rules, are
+ * an indicator to us that we should separate the national prefix from the
+ * number when formatting.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ self.NATIONAL_PREFIX_SEPARATORS_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:@"[- ]" options:0 error:&aError];
+
+ /**
+ * A pattern that is used to match character classes in regular expressions.
+ * An example of a character class is [1-4].
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ self.CHARACTER_CLASS_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:@"\\[([^\\[\\]])*\\]" options:0 error:&aError];
+
+ /**
+ * Any digit in a regular expression that actually denotes a digit. For
+ * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
+ * (8 and 0) are standalone digits, but the rest are not.
+ * Two look-aheads are needed because the number following \\d could be a
+ * two-digit number, since the phone number can be as long as 15 digits.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ self.STANDALONE_DIGIT_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:@"\\d(?=[^,}][^,}])" options:0 error:&aError];
+
+ /**
+ * A pattern that is used to determine if a numberFormat under availableFormats
+ * is eligible to be used by the AYTF. It is eligible when the format element
+ * under numberFormat contains groups of the dollar sign followed by a single
+ * digit, separated by valid phone number punctuation. This prevents invalid
+ * punctuation (such as the star sign in Israeli star numbers) getting into the
+ * output of the AYTF.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+ NSString *eligible_format = @"^[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~]*(\\$\\d[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~]*)+$";
+ self.ELIGIBLE_FORMAT_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:eligible_format options:0 error:&aError];
+
+ /**
+ * The pattern from numberFormat that is currently used to create
+ * formattingTemplate.
+ * @type {string}
+ * @private
+ */
+ self.currentFormattingPattern_ = @"";
+
+ /**
+ * @type {!goog.string.StringBuffer}
+ * @private
+ */
+ self.accruedInput_ = [NSMutableString stringWithString:@""];
+
+ /**
+ * @type {!goog.string.StringBuffer}
+ * @private
+ */
+ self.accruedInputWithoutFormatting_ = [NSMutableString stringWithString:@""];
+
+ /**
+ * This indicates whether AsYouTypeFormatter is currently doing the
+ * formatting.
+ * @type {BOOL}
+ * @private
+ */
+ self.ableToFormat_ = YES;
+
+ /**
+ * Set to YES when users enter their own formatting. AsYouTypeFormatter will
+ * do no formatting at all when this is set to YES.
+ * @type {BOOL}
+ * @private
+ */
+ self.inputHasFormatting_ = NO;
+
+ /**
+ * This is set to YES when we know the user is entering a full national
+ * significant number, since we have either detected a national prefix or an
+ * international dialing prefix. When this is YES, we will no longer use
+ * local number formatting patterns.
+ * @type {BOOL}
+ * @private
+ */
+ self.isCompleteNumber_ = NO;
+
+ /**
+ * @type {BOOL}
+ * @private
+ */
+ self.isExpectingCountryCallingCode_ = NO;
+
+ /**
+ * @type {number}
+ * @private
+ */
+ self.lastMatchPosition_ = 0;
+
+ /**
+ * The position of a digit upon which inputDigitAndRememberPosition is most
+ * recently invoked, as found in the original sequence of characters the user
+ * entered.
+ * @type {number}
+ * @private
+ */
+ self.originalPosition_ = 0;
+
+ /**
+ * The position of a digit upon which inputDigitAndRememberPosition is most
+ * recently invoked, as found in accruedInputWithoutFormatting.
+ * entered.
+ * @type {number}
+ * @private
+ */
+ self.positionToRemember_ = 0;
+
+ /**
+ * This contains anything that has been entered so far preceding the national
+ * significant number, and it is formatted (e.g. with space inserted). For
+ * example, this can contain IDD, country code, and/or NDD, etc.
+ * @type {!goog.string.StringBuffer}
+ * @private
+ */
+ self.prefixBeforeNationalNumber_ = [NSMutableString stringWithString:@""];
+
+ /**
+ * @type {BOOL}
+ * @private
+ */
+ self.shouldAddSpaceAfterNationalPrefix_ = NO;
+
+ /**
+ * This contains the national prefix that has been extracted. It contains only
+ * digits without formatting.
+ * @type {string}
+ * @private
+ */
+ self.nationalPrefixExtracted_ = @"";
+
+ /**
+ * @type {!goog.string.StringBuffer}
+ * @private
+ */
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+
+ /**
+ * @type {Array.<i18n.phonenumbers.NumberFormat>}
+ * @private
+ */
+ self.possibleFormats_ = [[NSMutableArray alloc] init];
+ }
+
+ return self;
+}
+
+/**
+ * Constructs an AsYouTypeFormatter for the specific region.
+ *
+ * @param {string} regionCode the ISO 3166-1 two-letter region code that denotes
+ * the region where the phone number is being entered.
+ * @constructor
+ */
+
+- (id)initWithRegionCode:(NSString*)regionCode
+{
+ self = [self init];
+
+ if (self) {
+ /**
+ * @type {string}
+ * @private
+ */
+ self.defaultCountry_ = regionCode;
+ self.currentMetaData_ = [self getMetadataForRegion_:self.defaultCountry_];
+ /**
+ * @type {i18n.phonenumbers.PhoneMetadata}
+ * @private
+ */
+ self.defaultMetaData_ = self.currentMetaData_;
+
+ /**
+ * @const
+ * @type {i18n.phonenumbers.PhoneMetadata}
+ * @private
+ */
+ self.EMPTY_METADATA_ = [[NBPhoneMetaData alloc] init];
+ [self.EMPTY_METADATA_ setInternationalPrefix:@"NA"];
+ }
+
+ return self;
+}
+
+- (id)initWithRegionCodeForTest:(NSString*)regionCode
+{
+ self = [self init];
+
+ if (self) {
+ self.phoneUtil_ = [NBPhoneNumberUtil sharedInstanceForTest];
+
+ self.defaultCountry_ = regionCode;
+ self.currentMetaData_ = [self getMetadataForRegion_:self.defaultCountry_];
+ self.defaultMetaData_ = self.currentMetaData_;
+ self.EMPTY_METADATA_ = [[NBPhoneMetaData alloc] init];
+ [self.EMPTY_METADATA_ setInternationalPrefix:@"NA"];
+ }
+
+ return self;
+}
+
+/**
+ * The metadata needed by this class is the same for all regions sharing the
+ * same country calling code. Therefore, we return the metadata for "main"
+ * region for this country calling code.
+ * @param {string} regionCode an ISO 3166-1 two-letter region code.
+ * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
+ * @private
+ */
+- (NBPhoneMetaData*)getMetadataForRegion_:(NSString*)regionCode
+{
+
+ /** @type {number} */
+ NSNumber *countryCallingCode = [self.phoneUtil_ getCountryCodeForRegion:regionCode];
+ /** @type {string} */
+ NSString *mainCountry = [self.phoneUtil_ getRegionCodeForCountryCode:countryCallingCode];
+ /** @type {i18n.phonenumbers.PhoneMetadata} */
+ NBPhoneMetaData *metadata = [self.phoneUtil_ getMetadataForRegion:mainCountry];
+ if (metadata != nil) {
+ return metadata;
+ }
+ // Set to a default instance of the metadata. This allows us to function with
+ // an incorrect region code, even if formatting only works for numbers
+ // specified with '+'.
+ return self.EMPTY_METADATA_;
+};
+
+
+/**
+ * @return {BOOL} YES if a new template is created as opposed to reusing the
+ * existing template.
+ * @private
+ */
+- (BOOL)maybeCreateNewTemplate_
+{
+ // When there are multiple available formats, the formatter uses the first
+ // format where a formatting template could be created.
+ /** @type {number} */
+ NSUInteger possibleFormatsLength = [self.possibleFormats_ count];
+ for (NSUInteger i = 0; i < possibleFormatsLength; ++i)
+ {
+ /** @type {i18n.phonenumbers.NumberFormat} */
+ NBNumberFormat *numberFormat = [self.possibleFormats_ safeObjectAtIndex:i];
+ /** @type {string} */
+ NSString *pattern = numberFormat.pattern;
+
+ if ([self.currentFormattingPattern_ isEqualToString:pattern]) {
+ return NO;
+ }
+
+ if ([self createFormattingTemplate_:numberFormat ])
+ {
+ self.currentFormattingPattern_ = pattern;
+ NSRange nationalPrefixRange = NSMakeRange(0, [numberFormat.nationalPrefixFormattingRule length]);
+ if (nationalPrefixRange.length > 0) {
+ NSTextCheckingResult *matchResult =
+ [self.NATIONAL_PREFIX_SEPARATORS_PATTERN_ firstMatchInString:numberFormat.nationalPrefixFormattingRule
+ options:0
+ range:nationalPrefixRange];
+ self.shouldAddSpaceAfterNationalPrefix_ = (matchResult != nil);
+ } else {
+ self.shouldAddSpaceAfterNationalPrefix_ = NO;
+ }
+ // With a new formatting template, the matched position using the old
+ // template needs to be reset.
+ self.lastMatchPosition_ = 0;
+ return YES;
+ }
+ }
+ self.ableToFormat_ = NO;
+ return NO;
+};
+
+
+/**
+ * @param {string} leadingThreeDigits first three digits of entered number.
+ * @private
+ */
+- (void)getAvailableFormats_:(NSString*)leadingThreeDigits
+{
+ /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
+ BOOL isIntlNumberFormats = (self.isCompleteNumber_ && self.currentMetaData_.intlNumberFormats.count > 0);
+ NSMutableArray *formatList = isIntlNumberFormats ? self.currentMetaData_.intlNumberFormats : self.currentMetaData_.numberFormats;
+
+ /** @type {number} */
+ NSUInteger formatListLength = formatList.count;
+
+ for (NSUInteger i = 0; i < formatListLength; ++i)
+ {
+ /** @type {i18n.phonenumbers.NumberFormat} */
+ NBNumberFormat *format = [formatList safeObjectAtIndex:i];
+ /** @type {BOOL} */
+ BOOL nationalPrefixIsUsedByCountry = (self.currentMetaData_.nationalPrefix && self.currentMetaData_.nationalPrefix.length > 0);
+
+ if (!nationalPrefixIsUsedByCountry || self.isCompleteNumber_ || format.nationalPrefixOptionalWhenFormatting ||
+ [self.phoneUtil_ formattingRuleHasFirstGroupOnly:format.nationalPrefixFormattingRule])
+ {
+ if ([self isFormatEligible_:format.format]) {
+ [self.possibleFormats_ addObject:format];
+ }
+ }
+ }
+
+ [self narrowDownPossibleFormats_:leadingThreeDigits];
+};
+
+
+/**
+ * @param {string} format
+ * @return {BOOL}
+ * @private
+ */
+- (BOOL)isFormatEligible_:(NSString*)format
+{
+ NSTextCheckingResult *matchResult =
+ [self.ELIGIBLE_FORMAT_PATTERN_ firstMatchInString:format options:0 range:NSMakeRange(0, [format length])];
+ return (matchResult != nil);
+};
+
+
+/**
+ * @param {string} leadingDigits
+ * @private
+ */
+- (void)narrowDownPossibleFormats_:(NSString *)leadingDigits
+{
+ /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
+ NSMutableArray *possibleFormats = [[NSMutableArray alloc] init];
+ /** @type {number} */
+ NSUInteger indexOfLeadingDigitsPattern = leadingDigits.length - self.MIN_LEADING_DIGITS_LENGTH_;
+ /** @type {number} */
+ NSUInteger possibleFormatsLength = self.possibleFormats_.count;
+ for (NSUInteger i = 0; i < possibleFormatsLength; ++i)
+ {
+ /** @type {i18n.phonenumbers.NumberFormat} */
+ NBNumberFormat *format = [self.possibleFormats_ safeObjectAtIndex:i];
+ if (format.leadingDigitsPatterns.count > indexOfLeadingDigitsPattern)
+ {
+ /** @type {string} */
+ NSString *leadingDigitsPattern = [format.leadingDigitsPatterns safeObjectAtIndex:indexOfLeadingDigitsPattern];
+
+ if ([self.phoneUtil_ stringPositionByRegex:leadingDigits regex:leadingDigitsPattern] == 0)
+ {
+ [possibleFormats addObject:format];
+ }
+ } else {
+ // else the particular format has no more specific leadingDigitsPattern,
+ // and it should be retained.
+ [possibleFormats addObject:[self.possibleFormats_ safeObjectAtIndex:i]];
+ }
+ }
+ self.possibleFormats_ = possibleFormats;
+};
+
+
+/**
+ * @param {i18n.phonenumbers.NumberFormat} format
+ * @return {BOOL}
+ * @private
+ */
+- (BOOL)createFormattingTemplate_:(NBNumberFormat*)format
+{
+ /** @type {string} */
+ NSString *numberPattern = format.pattern;
+
+ // The formatter doesn't format numbers when numberPattern contains '|', e.g.
+ // (20|3)\d{4}. In those cases we quickly return.
+ NSRange stringRange = [numberPattern rangeOfString:@"|"];
+ if (stringRange.location != NSNotFound) {
+ return NO;
+ }
+
+ // Replace anything in the form of [..] with \d
+ numberPattern = [self.CHARACTER_CLASS_PATTERN_ stringByReplacingMatchesInString:numberPattern
+ options:0 range:NSMakeRange(0, [numberPattern length])
+ withTemplate:@"\\\\d"];
+
+ // Replace any standalone digit (not the one in d{}) with \d
+ numberPattern = [self.STANDALONE_DIGIT_PATTERN_ stringByReplacingMatchesInString:numberPattern
+ options:0 range:NSMakeRange(0, [numberPattern length])
+ withTemplate:@"\\\\d"];
+ self.formattingTemplate_ = [NSMutableString stringWithString:@""];
+
+ /** @type {string} */
+ NSString *tempTemplate = [self getFormattingTemplate_:numberPattern numberFormat:format.format];
+ if (tempTemplate.length > 0) {
+ [self.formattingTemplate_ appendString:tempTemplate];
+ return YES;
+ }
+ return NO;
+};
+
+
+/**
+ * Gets a formatting template which can be used to efficiently format a
+ * partial number where digits are added one by one.
+ *
+ * @param {string} numberPattern
+ * @param {string} numberFormat
+ * @return {string}
+ * @private
+ */
+- (NSString*)getFormattingTemplate_:(NSString*)numberPattern numberFormat:(NSString*)numberFormat
+{
+ // Creates a phone number consisting only of the digit 9 that matches the
+ // numberPattern by applying the pattern to the longestPhoneNumber string.
+ /** @type {string} */
+ NSString *longestPhoneNumber = @"999999999999999";
+
+ /** @type {Array.<string>} */
+ NSArray *m = [self.phoneUtil_ matchedStringByRegex:longestPhoneNumber regex:numberPattern];
+
+ // this match will always succeed
+ /** @type {string} */
+ NSString *aPhoneNumber = [m safeObjectAtIndex:0];
+ // No formatting template can be created if the number of digits entered so
+ // far is longer than the maximum the current formatting rule can accommodate.
+ if (aPhoneNumber.length < self.nationalNumber_.length) {
+ return @"";
+ }
+ // Formats the number according to numberFormat
+ /** @type {string} */
+ NSString *template = [self.phoneUtil_ replaceStringByRegex:aPhoneNumber regex:numberPattern withTemplate:numberFormat];
+
+ // Replaces each digit with character DIGIT_PLACEHOLDER
+ template = [self.phoneUtil_ replaceStringByRegex:template regex:@"9" withTemplate:self.DIGIT_PLACEHOLDER_];
+ return template;
+};
+
+
+/**
+ * Clears the internal state of the formatter, so it can be reused.
+ */
+- (void)clear
+{
+ self.currentOutput_ = @"";
+ self.accruedInput_ = [NSMutableString stringWithString:@""];
+ self.accruedInputWithoutFormatting_ = [NSMutableString stringWithString:@""];
+ self.formattingTemplate_ = [NSMutableString stringWithString:@""];
+ self.lastMatchPosition_ = 0;
+ self.currentFormattingPattern_ = @"";
+ self.prefixBeforeNationalNumber_ = [NSMutableString stringWithString:@""];
+ self.nationalPrefixExtracted_ = @"";
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+ self.ableToFormat_ = YES;
+ self.inputHasFormatting_ = NO;
+ self.positionToRemember_ = 0;
+ self.originalPosition_ = 0;
+ self.isCompleteNumber_ = NO;
+ self.isExpectingCountryCallingCode_ = NO;
+ [self.possibleFormats_ removeAllObjects];
+ self.shouldAddSpaceAfterNationalPrefix_ = NO;
+
+ if (self.currentMetaData_ != self.defaultMetaData_) {
+ self.currentMetaData_ = [self getMetadataForRegion_:self.defaultCountry_];
+ }
+}
+
+
+/**
+ * Formats a phone number on-the-fly as each digit is entered.
+ *
+ * @param {string} nextChar the most recently entered digit of a phone number.
+ * Formatting characters are allowed, but as soon as they are encountered
+ * this method formats the number as entered and not 'as you type' anymore.
+ * Full width digits and Arabic-indic digits are allowed, and will be shown
+ * as they are.
+ * @return {string} the partially formatted phone number.
+ */
+- (NSString*)inputDigit:(NSString*)nextChar
+{
+ self.currentOutput_ = [self inputDigitWithOptionToRememberPosition_:nextChar rememberPosition:NO];
+ return self.currentOutput_;
+}
+
+
+/**
+ * Same as {@link #inputDigit}, but remembers the position where
+ * {@code nextChar} is inserted, so that it can be retrieved later by using
+ * {@link #getRememberedPosition}. The remembered position will be automatically
+ * adjusted if additional formatting characters are later inserted/removed in
+ * front of {@code nextChar}.
+ *
+ * @param {string} nextChar
+ * @return {string}
+ */
+- (NSString*)inputDigitAndRememberPosition:(NSString*)nextChar
+{
+ self.currentOutput_ = [self inputDigitWithOptionToRememberPosition_:nextChar rememberPosition:YES];
+ return self.currentOutput_;
+};
+
+
+/**
+ * @param {string} nextChar
+ * @param {BOOL} rememberPosition
+ * @return {string}
+ * @private
+ */
+- (NSString*)inputDigitWithOptionToRememberPosition_:(NSString*)nextChar rememberPosition:(BOOL)rememberPosition
+{
+ [self.accruedInput_ appendString:nextChar];
+
+ if (rememberPosition) {
+ self.originalPosition_ = self.accruedInput_.length;
+ }
+
+ // We do formatting on-the-fly only when each character entered is either a
+ // digit, or a plus sign (accepted at the start of the number only).
+ if (![self isDigitOrLeadingPlusSign_:nextChar])
+ {
+ self.ableToFormat_ = NO;
+ self.inputHasFormatting_ = YES;
+ } else {
+ nextChar = [self normalizeAndAccrueDigitsAndPlusSign_:nextChar rememberPosition:rememberPosition];
+ }
+
+ if (!self.ableToFormat_) {
+ // When we are unable to format because of reasons other than that
+ // formatting chars have been entered, it can be due to really long IDDs or
+ // NDDs. If that is the case, we might be able to do formatting again after
+ // extracting them.
+ if (self.inputHasFormatting_) {
+ return [NSString stringWithString:self.accruedInput_];
+ }
+ else if ([self attemptToExtractIdd_]) {
+ if ([self attemptToExtractCountryCallingCode_]) {
+ return [self attemptToChoosePatternWithPrefixExtracted_];
+ }
+ }
+ else if ([self ableToExtractLongerNdd_]) {
+ // Add an additional space to separate long NDD and national significant
+ // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
+ // to YES, since we don't want this to change later when we choose
+ // formatting templates.
+ [self.prefixBeforeNationalNumber_ appendString:[NSString stringWithFormat: @"%@", self.SEPARATOR_BEFORE_NATIONAL_NUMBER_]];
+ return [self attemptToChoosePatternWithPrefixExtracted_];
+ }
+ return self.accruedInput_;
+ }
+
+ // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
+ // digits (the plus sign is counted as a digit as well for this purpose) have
+ // been entered.
+ switch (self.accruedInputWithoutFormatting_.length)
+ {
+ case 0:
+ case 1:
+ case 2:
+ return self.accruedInput_;
+ case 3:
+ if ([self attemptToExtractIdd_]) {
+ self.isExpectingCountryCallingCode_ = YES;
+ } else {
+ // No IDD or plus sign is found, might be entering in national format.
+ self.nationalPrefixExtracted_ = [self removeNationalPrefixFromNationalNumber_];
+ return [self attemptToChooseFormattingPattern_];
+ }
+ default:
+ if (self.isExpectingCountryCallingCode_) {
+ if ([self attemptToExtractCountryCallingCode_]) {
+ self.isExpectingCountryCallingCode_ = NO;
+ }
+ return [NSString stringWithFormat:@"%@%@", self.prefixBeforeNationalNumber_, self.nationalNumber_];
+ }
+
+ if (self.possibleFormats_.count > 0) {
+ // The formatting pattern is already chosen.
+ /** @type {string} */
+ NSString *tempNationalNumber = [self inputDigitHelper_:nextChar];
+ // See if the accrued digits can be formatted properly already. If not,
+ // use the results from inputDigitHelper, which does formatting based on
+ // the formatting pattern chosen.
+ /** @type {string} */
+ NSString *formattedNumber = [self attemptToFormatAccruedDigits_];
+ if (formattedNumber.length > 0) {
+ return formattedNumber;
+ }
+
+ [self narrowDownPossibleFormats_:self.nationalNumber_];
+
+ if ([self maybeCreateNewTemplate_]) {
+ return [self inputAccruedNationalNumber_];
+ }
+
+ return self.ableToFormat_ ? [self appendNationalNumber_:tempNationalNumber] : self.accruedInput_;
+ }
+ else {
+ return [self attemptToChooseFormattingPattern_];
+ }
+ }
+};
+
+
+/**
+ * @return {string}
+ * @private
+ */
+- (NSString*)attemptToChoosePatternWithPrefixExtracted_
+{
+ self.ableToFormat_ = YES;
+ self.isExpectingCountryCallingCode_ = NO;
+ [self.possibleFormats_ removeAllObjects];
+ return [self attemptToChooseFormattingPattern_];
+};
+
+
+/**
+ * Some national prefixes are a substring of others. If extracting the shorter
+ * NDD doesn't result in a number we can format, we try to see if we can extract
+ * a longer version here.
+ * @return {BOOL}
+ * @private
+ */
+- (BOOL)ableToExtractLongerNdd_
+{
+ if (self.nationalPrefixExtracted_.length > 0)
+ {
+ // Put the extracted NDD back to the national number before attempting to
+ // extract a new NDD.
+ /** @type {string} */
+ NSString *nationalNumberStr = [NSString stringWithString:self.nationalNumber_];
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.nationalNumber_ appendString:self.nationalPrefixExtracted_];
+ [self.nationalNumber_ appendString:nationalNumberStr];
+ // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
+ // cannot simply set it to empty string because people sometimes incorrectly
+ // enter national prefix after the country code, e.g. +44 (0)20-1234-5678.
+ /** @type {string} */
+ NSString *prefixBeforeNationalNumberStr = [NSString stringWithString:self.prefixBeforeNationalNumber_];
+ NSRange lastRange = [prefixBeforeNationalNumberStr rangeOfString:self.nationalPrefixExtracted_ options:NSBackwardsSearch];
+ /** @type {number} */
+ NSUInteger indexOfPreviousNdd = lastRange.location;
+ self.prefixBeforeNationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.prefixBeforeNationalNumber_ appendString:[prefixBeforeNationalNumberStr substringWithRange:NSMakeRange(0, indexOfPreviousNdd)]];
+ }
+
+ return self.nationalPrefixExtracted_ != [self removeNationalPrefixFromNationalNumber_];
+};
+
+
+/**
+ * @param {string} nextChar
+ * @return {BOOL}
+ * @private
+ */
+- (BOOL)isDigitOrLeadingPlusSign_:(NSString*)nextChar
+{
+ NSString *digitPattern = [NSString stringWithFormat:@"([%@])", [self.phoneUtil_ VALID_DIGITS_STRING]];
+ NSString *plusPattern = [NSString stringWithFormat:@"[%@]+", [self.phoneUtil_ PLUS_CHARS_]];
+
+ BOOL isDigitPattern = [[self.phoneUtil_ matchesByRegex:nextChar regex:digitPattern] count] > 0;
+ BOOL isPlusPattern = [[self.phoneUtil_ matchesByRegex:nextChar regex:plusPattern] count] > 0;
+
+ return isDigitPattern || (self.accruedInput_.length == 1 && isPlusPattern);
+};
+
+
+/**
+ * Check to see if there is an exact pattern match for these digits. If so, we
+ * should use this instead of any other formatting template whose
+ * leadingDigitsPattern also matches the input.
+ * @return {string}
+ * @private
+ */
+- (NSString*)attemptToFormatAccruedDigits_
+{
+ /** @type {string} */
+ NSString *nationalNumber = [NSString stringWithString:self.nationalNumber_];
+
+ /** @type {number} */
+ NSUInteger possibleFormatsLength = self.possibleFormats_.count;
+ for (NSUInteger i = 0; i < possibleFormatsLength; ++i)
+ {
+ /** @type {i18n.phonenumbers.NumberFormat} */
+ NBNumberFormat *numberFormat = self.possibleFormats_[i];
+ /** @type {string} */
+ NSString * pattern = numberFormat.pattern;
+ /** @type {RegExp} */
+ NSString *patternRegExp = [NSString stringWithFormat:@"^(?:%@)$", pattern];
+ BOOL isPatternRegExp = [[self.phoneUtil_ matchesByRegex:nationalNumber regex:patternRegExp] count] > 0;
+ if (isPatternRegExp) {
+ if (numberFormat.nationalPrefixFormattingRule.length > 0) {
+ NSArray *matches = [self.NATIONAL_PREFIX_SEPARATORS_PATTERN_ matchesInString:numberFormat.nationalPrefixFormattingRule
+ options:0
+ range:NSMakeRange(0, numberFormat.nationalPrefixFormattingRule.length)];
+ self.shouldAddSpaceAfterNationalPrefix_ = [matches count] > 0;
+ } else {
+ self.shouldAddSpaceAfterNationalPrefix_ = NO;
+ }
+
+ /** @type {string} */
+ NSString *formattedNumber = [self.phoneUtil_ replaceStringByRegex:nationalNumber
+ regex:pattern
+ withTemplate:numberFormat.format];
+ return [self appendNationalNumber_:formattedNumber];
+ }
+ }
+ return @"";
+};
+
+
+/**
+ * Combines the national number with any prefix (IDD/+ and country code or
+ * national prefix) that was collected. A space will be inserted between them if
+ * the current formatting template indicates this to be suitable.
+ * @param {string} nationalNumber The number to be appended.
+ * @return {string} The combined number.
+ * @private
+ */
+- (NSString*)appendNationalNumber_:(NSString*)nationalNumber
+{
+ /** @type {number} */
+ NSUInteger prefixBeforeNationalNumberLength = self.prefixBeforeNationalNumber_.length;
+ unichar blank_char = [self.SEPARATOR_BEFORE_NATIONAL_NUMBER_ characterAtIndex:0];
+ if (self.shouldAddSpaceAfterNationalPrefix_ && prefixBeforeNationalNumberLength > 0 &&
+ [self.prefixBeforeNationalNumber_ characterAtIndex:prefixBeforeNationalNumberLength - 1] != blank_char)
+ {
+ // We want to add a space after the national prefix if the national prefix
+ // formatting rule indicates that this would normally be done, with the
+ // exception of the case where we already appended a space because the NDD
+ // was surprisingly long.
+
+ return [NSString stringWithFormat:@"%@%@%@", self.prefixBeforeNationalNumber_, self.SEPARATOR_BEFORE_NATIONAL_NUMBER_, nationalNumber];
+ } else {
+ return [NSString stringWithFormat:@"%@%@", self.prefixBeforeNationalNumber_, nationalNumber];
+ }
+};
+
+
+/**
+ * Returns the current position in the partially formatted phone number of the
+ * character which was previously passed in as the parameter of
+ * {@link #inputDigitAndRememberPosition}.
+ *
+ * @return {number}
+ */
+- (NSInteger)getRememberedPosition
+{
+ if (!self.ableToFormat_) {
+ return self.originalPosition_;
+ }
+ /** @type {number} */
+ NSUInteger accruedInputIndex = 0;
+ /** @type {number} */
+ NSUInteger currentOutputIndex = 0;
+ /** @type {string} */
+ NSString *accruedInputWithoutFormatting = self.accruedInputWithoutFormatting_;
+ /** @type {string} */
+ NSString *currentOutput = self.currentOutput_;
+
+ while (accruedInputIndex < self.positionToRemember_ && currentOutputIndex < currentOutput.length)
+ {
+ if ([accruedInputWithoutFormatting characterAtIndex:accruedInputIndex] == [currentOutput characterAtIndex:currentOutputIndex])
+ {
+ accruedInputIndex++;
+ }
+ currentOutputIndex++;
+ }
+ return currentOutputIndex;
+};
+
+
+/**
+ * Attempts to set the formatting template and returns a string which contains
+ * the formatted version of the digits entered so far.
+ *
+ * @return {string}
+ * @private
+ */
+- (NSString*)attemptToChooseFormattingPattern_
+{
+ /** @type {string} */
+ NSString *nationalNumber = [self.nationalNumber_ copy];
+ // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
+ // digits of national number (excluding national prefix) have been entered.
+ if (nationalNumber.length >= self.MIN_LEADING_DIGITS_LENGTH_) {
+ [self getAvailableFormats_:[nationalNumber substringWithRange:NSMakeRange(0, self.MIN_LEADING_DIGITS_LENGTH_)]];
+ return [self maybeCreateNewTemplate_] ? [self inputAccruedNationalNumber_] : self.accruedInput_;
+ } else {
+ return [self appendNationalNumber_:nationalNumber];
+ }
+}
+
+
+/**
+ * Invokes inputDigitHelper on each digit of the national number accrued, and
+ * returns a formatted string in the end.
+ *
+ * @return {string}
+ * @private
+ */
+- (NSString*)inputAccruedNationalNumber_
+{
+ /** @type {string} */
+ NSString *nationalNumber = [self.nationalNumber_ copy];
+ /** @type {number} */
+ NSUInteger lengthOfNationalNumber = nationalNumber.length;
+ if (lengthOfNationalNumber > 0) {
+ /** @type {string} */
+ NSString *tempNationalNumber = @"";
+ for (NSUInteger i = 0; i < lengthOfNationalNumber; i++)
+ {
+ tempNationalNumber = [self inputDigitHelper_:[NSString stringWithFormat: @"%C", [nationalNumber characterAtIndex:i]]];
+ }
+ return self.ableToFormat_ ? [self appendNationalNumber_:tempNationalNumber] : self.accruedInput_;
+ } else {
+ return self.prefixBeforeNationalNumber_;
+ }
+};
+
+
+/**
+ * @return {BOOL} YES if the current country is a NANPA country and the
+ * national number begins with the national prefix.
+ * @private
+ */
+- (BOOL)isNanpaNumberWithNationalPrefix_
+{
+ // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
+ // prefix. The reason is that national significant numbers in NANPA always
+ // start with [2-9] after the national prefix. Numbers beginning with 1[01]
+ // can only be short/emergency numbers, which don't need the national prefix.
+ if (![self.currentMetaData_.countryCode isEqualToNumber:@1]) {
+ return NO;
+ }
+
+ /** @type {string} */
+ NSString *nationalNumber = [self.nationalNumber_ copy];
+ return ([nationalNumber characterAtIndex:0] == '1') && ([nationalNumber characterAtIndex:1] != '0') &&
+ ([nationalNumber characterAtIndex:1] != '1');
+};
+
+
+/**
+ * Returns the national prefix extracted, or an empty string if it is not
+ * present.
+ * @return {string}
+ * @private
+ */
+- (NSString*)removeNationalPrefixFromNationalNumber_
+{
+ /** @type {string} */
+ NSString *nationalNumber = [self.nationalNumber_ copy];
+ /** @type {number} */
+ NSUInteger startOfNationalNumber = 0;
+
+ if ([self isNanpaNumberWithNationalPrefix_]) {
+ startOfNationalNumber = 1;
+ [self.prefixBeforeNationalNumber_ appendString:@"1"];
+ [self.prefixBeforeNationalNumber_ appendFormat:@"%@", self.SEPARATOR_BEFORE_NATIONAL_NUMBER_];
+ self.isCompleteNumber_ = YES;
+ }
+ else if (self.currentMetaData_.nationalPrefixForParsing != nil && self.currentMetaData_.nationalPrefixForParsing.length > 0)
+ {
+ /** @type {RegExp} */
+ NSString *nationalPrefixForParsing = [NSString stringWithFormat:@"^(?:%@)", self.currentMetaData_.nationalPrefixForParsing];
+ /** @type {Array.<string>} */
+ NSArray *m = [self.phoneUtil_ matchedStringByRegex:nationalNumber regex:nationalPrefixForParsing];
+ NSString *firstString = [m safeObjectAtIndex:0];
+ if (m != nil && firstString != nil && firstString.length > 0) {
+ // When the national prefix is detected, we use international formatting
+ // rules instead of national ones, because national formatting rules could
+ // contain local formatting rules for numbers entered without area code.
+ self.isCompleteNumber_ = YES;
+ startOfNationalNumber = firstString.length;
+ [self.prefixBeforeNationalNumber_ appendString:[nationalNumber substringWithRange:NSMakeRange(0, startOfNationalNumber)]];
+ }
+ }
+
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.nationalNumber_ appendString:[nationalNumber substringFromIndex:startOfNationalNumber]];
+ return [nationalNumber substringWithRange:NSMakeRange(0, startOfNationalNumber)];
+};
+
+
+/**
+ * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
+ * available, and places the remaining input into nationalNumber.
+ *
+ * @return {BOOL} YES when accruedInputWithoutFormatting begins with the
+ * plus sign or valid IDD for defaultCountry.
+ * @private
+ */
+- (BOOL)attemptToExtractIdd_
+{
+ /** @type {string} */
+ NSString *accruedInputWithoutFormatting = [self.accruedInputWithoutFormatting_ copy];
+ /** @type {RegExp} */
+ NSString *internationalPrefix = [NSString stringWithFormat:@"^(?:\\+|%@)", self.currentMetaData_.internationalPrefix];
+ /** @type {Array.<string>} */
+ NSArray *m = [self.phoneUtil_ matchedStringByRegex:accruedInputWithoutFormatting regex:internationalPrefix];
+
+ NSString *firstString = [m safeObjectAtIndex:0];
+
+ if (m != nil && firstString != nil && firstString.length > 0) {
+ self.isCompleteNumber_ = YES;
+ /** @type {number} */
+ NSUInteger startOfCountryCallingCode = firstString.length;
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.nationalNumber_ appendString:[accruedInputWithoutFormatting substringFromIndex:startOfCountryCallingCode]];
+ self.prefixBeforeNationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.prefixBeforeNationalNumber_ appendString:[accruedInputWithoutFormatting substringWithRange:NSMakeRange(0, startOfCountryCallingCode)]];
+
+ if ([accruedInputWithoutFormatting characterAtIndex:0] != '+')
+ {
+ [self.prefixBeforeNationalNumber_ appendString:[NSString stringWithFormat: @"%@", self.SEPARATOR_BEFORE_NATIONAL_NUMBER_]];
+ }
+ return YES;
+ }
+ return NO;
+};
+
+
+/**
+ * Extracts the country calling code from the beginning of nationalNumber to
+ * prefixBeforeNationalNumber when they are available, and places the remaining
+ * input into nationalNumber.
+ *
+ * @return {BOOL} YES when a valid country calling code can be found.
+ * @private
+ */
+- (BOOL)attemptToExtractCountryCallingCode_
+{
+ if (self.nationalNumber_.length == 0) {
+ return NO;
+ }
+
+ /** @type {!goog.string.StringBuffer} */
+ NSString *numberWithoutCountryCallingCode = @"";
+
+ /** @type {number} */
+ NSNumber *countryCode = [self.phoneUtil_ extractCountryCode:self.nationalNumber_ nationalNumber:&numberWithoutCountryCallingCode];
+
+ if ([countryCode isEqualToNumber:@0]) {
+ return NO;
+ }
+
+ self.nationalNumber_ = [NSMutableString stringWithString:@""];
+ [self.nationalNumber_ appendString:numberWithoutCountryCallingCode];
+
+ /** @type {string} */
+ NSString *newRegionCode = [self.phoneUtil_ getRegionCodeForCountryCode:countryCode];
+
+ if ([[self.phoneUtil_ REGION_CODE_FOR_NON_GEO_ENTITY] isEqualToString:newRegionCode]) {
+ self.currentMetaData_ = [self.phoneUtil_ getMetadataForNonGeographicalRegion:countryCode];
+ } else if (newRegionCode != self.defaultCountry_)
+ {
+ self.currentMetaData_ = [self getMetadataForRegion_:newRegionCode];
+ }
+
+ /** @type {string} */
+ NSString *countryCodeString = [NSString stringWithFormat:@"%@", countryCode];
+ [self.prefixBeforeNationalNumber_ appendString:countryCodeString];
+ [self.prefixBeforeNationalNumber_ appendString:[NSString stringWithFormat: @"%@", self.SEPARATOR_BEFORE_NATIONAL_NUMBER_]];
+ return YES;
+};
+
+
+/**
+ * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
+ * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
+ * version of digits), it is first normalized to the ASCII version. The return
+ * value is nextChar itself, or its normalized version, if nextChar is a digit
+ * in non-ASCII format. This method assumes its input is either a digit or the
+ * plus sign.
+ *
+ * @param {string} nextChar
+ * @param {BOOL} rememberPosition
+ * @return {string}
+ * @private
+ */
+- (NSString*)normalizeAndAccrueDigitsAndPlusSign_:(NSString *)nextChar rememberPosition:(BOOL)rememberPosition
+{
+ /** @type {string} */
+ NSString *normalizedChar;
+
+ if ([nextChar isEqualToString:@"+"]) {
+ normalizedChar = nextChar;
+ [self.accruedInputWithoutFormatting_ appendString:nextChar];
+ } else {
+ normalizedChar = [[self.phoneUtil_ DIGIT_MAPPINGS] objectForKey:nextChar];
+ [self.accruedInputWithoutFormatting_ appendString:normalizedChar];
+ [self.nationalNumber_ appendString:normalizedChar];
+ }
+
+ if (rememberPosition) {
+ self.positionToRemember_ = self.accruedInputWithoutFormatting_.length;
+ }
+
+ return normalizedChar;
+};
+
+
+/**
+ * @param {string} nextChar
+ * @return {string}
+ * @private
+ */
+- (NSString*)inputDigitHelper_:(NSString *)nextChar
+{
+ /** @type {string} */
+ NSString *formattingTemplate = [self.formattingTemplate_ copy];
+ NSString *subedString = @"";
+
+ if (formattingTemplate.length > self.lastMatchPosition_) {
+ subedString = [formattingTemplate substringFromIndex:self.lastMatchPosition_];
+ }
+
+ if ([self.phoneUtil_ stringPositionByRegex:subedString regex:self.DIGIT_PLACEHOLDER_] >= 0) {
+ /** @type {number} */
+ NSUInteger digitPatternStart = [self.phoneUtil_ stringPositionByRegex:formattingTemplate regex:self.DIGIT_PLACEHOLDER_];
+
+ /** @type {string} */
+ NSRange tempRange = [formattingTemplate rangeOfString:self.DIGIT_PLACEHOLDER_];
+ NSString *tempTemplate = [formattingTemplate stringByReplacingOccurrencesOfString:self.DIGIT_PLACEHOLDER_
+ withString:nextChar
+ options:NSLiteralSearch
+ range:tempRange];
+ self.formattingTemplate_ = [NSMutableString stringWithString:@""];
+ [self.formattingTemplate_ appendString:tempTemplate];
+ self.lastMatchPosition_ = digitPatternStart;
+ return [tempTemplate substringWithRange:NSMakeRange(0, self.lastMatchPosition_ + 1)];
+ } else {
+ if (self.possibleFormats_.count == 1)
+ {
+ // More digits are entered than we could handle, and there are no other
+ // valid patterns to try.
+ self.ableToFormat_ = NO;
+ } // else, we just reset the formatting pattern.
+ self.currentFormattingPattern_ = @"";
+ return self.accruedInput_;
+ }
+};
+
+@end
diff --git a/libPhoneNumber/NBNumberFormat.h b/libPhoneNumber/NBNumberFormat.h
new file mode 100644
index 0000000..d2e8749
--- /dev/null
+++ b/libPhoneNumber/NBNumberFormat.h
@@ -0,0 +1,22 @@
+//
+// NBPhoneNumberFormat.h
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NBNumberFormat : NSObject
+
+// from phonemetadata.pb.js
+/* 1 */ @property (nonatomic, strong, readwrite) NSString *pattern;
+/* 2 */ @property (nonatomic, strong, readwrite) NSString *format;
+/* 3 */ @property (nonatomic, strong, readwrite) NSMutableArray *leadingDigitsPatterns;
+/* 4 */ @property (nonatomic, strong, readwrite) NSString *nationalPrefixFormattingRule;
+/* 6 */ @property (nonatomic, assign, readwrite) BOOL nationalPrefixOptionalWhenFormatting;
+/* 5 */ @property (nonatomic, strong, readwrite) NSString *domesticCarrierCodeFormattingRule;
+
+- (id)initWithData:(id)data;
+
+@end
diff --git a/libPhoneNumber/NBNumberFormat.m b/libPhoneNumber/NBNumberFormat.m
new file mode 100644
index 0000000..5f261a7
--- /dev/null
+++ b/libPhoneNumber/NBNumberFormat.m
@@ -0,0 +1,139 @@
+//
+// NBPhoneNumberFormat.m
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import "NBNumberFormat.h"
+#import "NBPhoneNumberDefines.h"
+
+@implementation NBNumberFormat
+
+@synthesize pattern, format, leadingDigitsPatterns, nationalPrefixFormattingRule, nationalPrefixOptionalWhenFormatting, domesticCarrierCodeFormattingRule;
+
+
+- (id)initWithData:(id)data
+{
+ self = [self init];
+
+ if (self && data != nil && [data isKindOfClass:[NSArray class]])
+ {
+ /* 1 */ self.pattern = [data safeObjectAtIndex:1];
+ /* 2 */ self.format = [data safeObjectAtIndex:2];
+ /* 3 */ self.leadingDigitsPatterns = [self stringArrayFromData:[data safeObjectAtIndex:3]]; // NSString array
+ /* 4 */ self.nationalPrefixFormattingRule = [data safeObjectAtIndex:4];
+ /* 6 */ self.nationalPrefixOptionalWhenFormatting = [[data safeObjectAtIndex:6] boolValue];
+ /* 5 */ self.domesticCarrierCodeFormattingRule = [data safeObjectAtIndex:5];
+ }
+
+ return self;
+}
+
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ self.nationalPrefixOptionalWhenFormatting = NO;
+ self.leadingDigitsPatterns = [[NSMutableArray alloc] init];
+ }
+
+ return self;
+}
+
+
+- (NSMutableArray*)stringArrayFromData:(id)data
+{
+ NSMutableArray *resArray = [[NSMutableArray alloc] init];
+ if (data != nil && [data isKindOfClass:[NSArray class]])
+ {
+ for (id numFormat in data)
+ {
+ if ([numFormat isKindOfClass:[NSString class]])
+ {
+ [resArray addObject:numFormat];
+ }
+ else
+ {
+ [resArray addObject:[numFormat stringValue]];
+ }
+ }
+ }
+
+ return resArray;
+}
+
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"[pattern:%@, format:%@, leadingDigitsPattern:%@, nationalPrefixFormattingRule:%@, nationalPrefixOptionalWhenFormatting:%@, domesticCarrierCodeFormattingRule:%@]",
+ self.pattern, self.format, self.leadingDigitsPatterns, self.nationalPrefixFormattingRule, self.nationalPrefixOptionalWhenFormatting?@"Y":@"N", self.domesticCarrierCodeFormattingRule];
+}
+
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ NBNumberFormat *phoneFormatCopy = [[NBNumberFormat allocWithZone:zone] init];
+
+ /*
+ 1 @property (nonatomic, strong, readwrite) NSString *pattern;
+ 2 @property (nonatomic, strong, readwrite) NSString *format;
+ 3 @property (nonatomic, strong, readwrite) NSString *leadingDigitsPattern;
+ 4 @property (nonatomic, strong, readwrite) NSString *nationalPrefixFormattingRule;
+ 6 @property (nonatomic, assign, readwrite) BOOL nationalPrefixOptionalWhenFormatting;
+ 5 @property (nonatomic, strong, readwrite) NSString *domesticCarrierCodeFormattingRule;
+ */
+
+ phoneFormatCopy.pattern = [self.pattern copy];
+ phoneFormatCopy.format = [self.format copy];
+ phoneFormatCopy.leadingDigitsPatterns = [self.leadingDigitsPatterns copy];
+ phoneFormatCopy.nationalPrefixFormattingRule = [self.nationalPrefixFormattingRule copy];
+ phoneFormatCopy.nationalPrefixOptionalWhenFormatting = self.nationalPrefixOptionalWhenFormatting;
+ phoneFormatCopy.domesticCarrierCodeFormattingRule = [self.domesticCarrierCodeFormattingRule copy];
+
+ return phoneFormatCopy;
+}
+
+
+- (id)initWithCoder:(NSCoder*)coder
+{
+ if (self = [super init])
+ {
+ self.pattern = [coder decodeObjectForKey:@"pattern"];
+ self.format = [coder decodeObjectForKey:@"format"];
+ self.leadingDigitsPatterns = [coder decodeObjectForKey:@"leadingDigitsPatterns"];
+ self.nationalPrefixFormattingRule = [coder decodeObjectForKey:@"nationalPrefixFormattingRule"];
+ self.nationalPrefixOptionalWhenFormatting = [[coder decodeObjectForKey:@"nationalPrefixOptionalWhenFormatting"] boolValue];
+ self.domesticCarrierCodeFormattingRule = [coder decodeObjectForKey:@"domesticCarrierCodeFormattingRule"];
+ }
+ return self;
+}
+
+
+- (void)encodeWithCoder:(NSCoder*)coder
+{
+ [coder encodeObject:self.pattern forKey:@"pattern"];
+ [coder encodeObject:self.format forKey:@"format"];
+ [coder encodeObject:self.leadingDigitsPatterns forKey:@"leadingDigitsPatterns"];
+ [coder encodeObject:self.nationalPrefixFormattingRule forKey:@"nationalPrefixFormattingRule"];
+ [coder encodeObject:[NSNumber numberWithBool:self.nationalPrefixOptionalWhenFormatting] forKey:@"nationalPrefixOptionalWhenFormatting"];
+ [coder encodeObject:self.domesticCarrierCodeFormattingRule forKey:@"domesticCarrierCodeFormattingRule"];
+}
+
+
+- (void)setData:(id)data
+{
+ if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSMutableArray class]])
+ {
+
+ }
+ else if ([data isKindOfClass:[NSDictionary class]] || [data isKindOfClass:[NSMutableDictionary class]])
+ {
+
+ }
+}
+
+@end
diff --git a/libPhoneNumber/NBPhoneMetaData.h b/libPhoneNumber/NBPhoneMetaData.h
new file mode 100644
index 0000000..6767492
--- /dev/null
+++ b/libPhoneNumber/NBPhoneMetaData.h
@@ -0,0 +1,45 @@
+//
+// M2PhoneMetaData.h
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import <Foundation/Foundation.h>
+
+@class NBPhoneNumberDesc, NBNumberFormat;
+
+@interface NBPhoneMetaData : NSObject
+
+// from phonemetadata.pb.js
+/* 1 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *generalDesc;
+/* 2 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *fixedLine;
+/* 3 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *mobile;
+/* 4 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *tollFree;
+/* 5 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *premiumRate;
+/* 6 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *sharedCost;
+/* 7 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *personalNumber;
+/* 8 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *voip;
+/* 21 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *pager;
+/* 25 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *uan;
+/* 27 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *emergency;
+/* 28 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *voicemail;
+/* 24 */ @property (nonatomic, strong, readwrite) NBPhoneNumberDesc *noInternationalDialling;
+/* 9 */ @property (nonatomic, strong, readwrite) NSString *codeID;
+/* 10 */ @property (nonatomic, strong, readwrite) NSNumber *countryCode;
+/* 11 */ @property (nonatomic, strong, readwrite) NSString *internationalPrefix;
+/* 17 */ @property (nonatomic, strong, readwrite) NSString *preferredInternationalPrefix;
+/* 12 */ @property (nonatomic, strong, readwrite) NSString *nationalPrefix;
+/* 13 */ @property (nonatomic, strong, readwrite) NSString *preferredExtnPrefix;
+/* 15 */ @property (nonatomic, strong, readwrite) NSString *nationalPrefixForParsing;
+/* 16 */ @property (nonatomic, strong, readwrite) NSString *nationalPrefixTransformRule;
+/* 18 */ @property (nonatomic, assign, readwrite) BOOL sameMobileAndFixedLinePattern;
+/* 19 */ @property (nonatomic, strong, readwrite) NSMutableArray *numberFormats;
+/* 20 */ @property (nonatomic, strong, readwrite) NSMutableArray *intlNumberFormats;
+/* 22 */ @property (nonatomic, assign, readwrite) BOOL mainCountryForCode;
+/* 23 */ @property (nonatomic, strong, readwrite) NSString *leadingDigits;
+/* 26 */ @property (nonatomic, assign, readwrite) BOOL leadingZeroPossible;
+
+- (void)buildData:(id)data;
+
+@end
diff --git a/libPhoneNumber/NBPhoneMetaData.m b/libPhoneNumber/NBPhoneMetaData.m
new file mode 100644
index 0000000..8aa30d8
--- /dev/null
+++ b/libPhoneNumber/NBPhoneMetaData.m
@@ -0,0 +1,458 @@
+//
+// NBPhoneMetaData.m
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import "NBPhoneMetaData.h"
+#import "NBPhoneNumberDesc.h"
+#import "NBNumberFormat.h"
+#import "NBPhoneNumberDefines.h"
+
+@implementation NBPhoneMetaData
+
+@synthesize generalDesc, fixedLine, mobile, tollFree, premiumRate, sharedCost, personalNumber, voip, pager, uan, emergency, voicemail, noInternationalDialling;
+@synthesize codeID, countryCode;
+@synthesize internationalPrefix, preferredInternationalPrefix, nationalPrefix, preferredExtnPrefix, nationalPrefixForParsing, nationalPrefixTransformRule, sameMobileAndFixedLinePattern, numberFormats, intlNumberFormats, mainCountryForCode, leadingDigits, leadingZeroPossible;
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ [self setNumberFormats:[[NSMutableArray alloc] init]];
+ [self setIntlNumberFormats:[[NSMutableArray alloc] init]];
+
+ self.leadingZeroPossible = NO;
+ self.mainCountryForCode = NO;
+ }
+
+ return self;
+}
+
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"* codeID[%@] countryCode[%@] generalDesc[%@] fixedLine[%@] mobile[%@] tollFree[%@] premiumRate[%@] sharedCost[%@] personalNumber[%@] voip[%@] pager[%@] uan[%@] emergency[%@] voicemail[%@] noInternationalDialling[%@] internationalPrefix[%@] preferredInternationalPrefix[%@] nationalPrefix[%@] preferredExtnPrefix[%@] nationalPrefixForParsing[%@] nationalPrefixTransformRule[%@] sameMobileAndFixedLinePattern[%@] numberFormats[%@] intlNumberFormats[%@] mainCountryForCode[%@] leadingDigits[%@] leadingZeroPossible[%@]",
+ self.codeID, self.countryCode, self.generalDesc, self.fixedLine, self.mobile, self.tollFree, self.premiumRate, self.sharedCost, self.personalNumber, self.voip, self.pager, self.uan, self.emergency, self.voicemail, self.noInternationalDialling, self.internationalPrefix, self.preferredInternationalPrefix, self.nationalPrefix, self.preferredExtnPrefix, self.nationalPrefixForParsing, self.nationalPrefixTransformRule, self.sameMobileAndFixedLinePattern?@"Y":@"N", self.numberFormats, self.intlNumberFormats, self.mainCountryForCode?@"Y":@"N", self.leadingDigits, self.leadingZeroPossible?@"Y":@"N"];
+}
+
+
+- (void)buildData:(id)data
+{
+ if (data != nil && [data isKindOfClass:[NSArray class]] )
+ {
+ /* 1 */ self.generalDesc = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:1]];
+ /* 2 */ self.fixedLine = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:2]];
+ /* 3 */ self.mobile = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:3]];
+ /* 4 */ self.tollFree = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:4]];
+ /* 5 */ self.premiumRate = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:5]];
+ /* 6 */ self.sharedCost = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:6]];
+ /* 7 */ self.personalNumber = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:7]];
+ /* 8 */ self.voip = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:8]];
+ /* 21 */ self.pager = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:21]];
+ /* 25 */ self.uan = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:25]];
+ /* 27 */ self.emergency = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:27]];
+ /* 28 */ self.voicemail = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:28]];
+ /* 24 */ self.noInternationalDialling = [[NBPhoneNumberDesc alloc] initWithData:[data safeObjectAtIndex:24]];
+ /* 9 */ self.codeID = [data safeObjectAtIndex:9];
+ /* 10 */ self.countryCode = [data safeObjectAtIndex:10];
+ /* 11 */ self.internationalPrefix = [data safeObjectAtIndex:11];
+ /* 17 */ self.preferredInternationalPrefix = [data safeObjectAtIndex:17];
+ /* 12 */ self.nationalPrefix = [data safeObjectAtIndex:12];
+ /* 13 */ self.preferredExtnPrefix = [data safeObjectAtIndex:13];
+ /* 15 */ self.nationalPrefixForParsing = [data safeObjectAtIndex:15];
+ /* 16 */ self.nationalPrefixTransformRule = [data safeObjectAtIndex:16];
+ /* 18 */ self.sameMobileAndFixedLinePattern = [[data safeObjectAtIndex:18] boolValue];
+ /* 19 */ self.numberFormats = [self numberFormatArrayFromData:[data safeObjectAtIndex:19]]; // NBNumberFormat array
+ /* 20 */ self.intlNumberFormats = [self numberFormatArrayFromData:[data safeObjectAtIndex:20]]; // NBNumberFormat array
+ /* 22 */ self.mainCountryForCode = [[data safeObjectAtIndex:22] boolValue];
+ /* 23 */ self.leadingDigits = [data safeObjectAtIndex:23];
+ /* 26 */ self.leadingZeroPossible = [[data safeObjectAtIndex:26] boolValue];
+ }
+ else
+ {
+ NSLog(@"nil data or wrong data type");
+ }
+}
+
+
+- (id)initWithCoder:(NSCoder*)coder
+{
+ if (self = [super init])
+ {
+ self.generalDesc = [coder decodeObjectForKey:@"generalDesc"];
+ self.fixedLine = [coder decodeObjectForKey:@"fixedLine"];
+ self.mobile = [coder decodeObjectForKey:@"mobile"];
+ self.tollFree = [coder decodeObjectForKey:@"tollFree"];
+ self.premiumRate = [coder decodeObjectForKey:@"premiumRate"];
+ self.sharedCost = [coder decodeObjectForKey:@"sharedCost"];
+ self.personalNumber = [coder decodeObjectForKey:@"personalNumber"];
+ self.voip = [coder decodeObjectForKey:@"voip"];
+ self.pager = [coder decodeObjectForKey:@"pager"];
+ self.uan = [coder decodeObjectForKey:@"uan"];
+ self.emergency = [coder decodeObjectForKey:@"emergency"];
+ self.voicemail = [coder decodeObjectForKey:@"voicemail"];
+ self.noInternationalDialling = [coder decodeObjectForKey:@"noInternationalDialling"];
+ self.codeID = [coder decodeObjectForKey:@"codeID"];
+ self.countryCode = [coder decodeObjectForKey:@"countryCode"];
+ self.internationalPrefix = [coder decodeObjectForKey:@"internationalPrefix"];
+ self.preferredInternationalPrefix = [coder decodeObjectForKey:@"preferredInternationalPrefix"];
+ self.nationalPrefix = [coder decodeObjectForKey:@"nationalPrefix"];
+ self.preferredExtnPrefix = [coder decodeObjectForKey:@"preferredExtnPrefix"];
+ self.nationalPrefixForParsing = [coder decodeObjectForKey:@"nationalPrefixForParsing"];
+ self.nationalPrefixTransformRule = [coder decodeObjectForKey:@"nationalPrefixTransformRule"];
+ self.sameMobileAndFixedLinePattern = [[coder decodeObjectForKey:@"sameMobileAndFixedLinePattern"] boolValue];
+ self.numberFormats = [coder decodeObjectForKey:@"numberFormats"];
+ self.intlNumberFormats = [coder decodeObjectForKey:@"intlNumberFormats"];
+ self.mainCountryForCode = [[coder decodeObjectForKey:@"mainCountryForCode"] boolValue];
+ self.leadingDigits = [coder decodeObjectForKey:@"leadingDigits"];
+ self.leadingZeroPossible = [[coder decodeObjectForKey:@"leadingZeroPossible"] boolValue];
+ }
+ return self;
+}
+
+
+- (void)encodeWithCoder:(NSCoder*)coder
+{
+ [coder encodeObject:self.generalDesc forKey:@"generalDesc"];
+ [coder encodeObject:self.fixedLine forKey:@"fixedLine"];
+ [coder encodeObject:self.mobile forKey:@"mobile"];
+ [coder encodeObject:self.tollFree forKey:@"tollFree"];
+ [coder encodeObject:self.premiumRate forKey:@"premiumRate"];
+ [coder encodeObject:self.sharedCost forKey:@"sharedCost"];
+ [coder encodeObject:self.personalNumber forKey:@"personalNumber"];
+ [coder encodeObject:self.voip forKey:@"voip"];
+ [coder encodeObject:self.pager forKey:@"pager"];
+ [coder encodeObject:self.uan forKey:@"uan"];
+ [coder encodeObject:self.emergency forKey:@"emergency"];
+ [coder encodeObject:self.voicemail forKey:@"voicemail"];
+ [coder encodeObject:self.noInternationalDialling forKey:@"noInternationalDialling"];
+ [coder encodeObject:self.codeID forKey:@"codeID"];
+ [coder encodeObject:self.countryCode forKey:@"countryCode"];
+ [coder encodeObject:self.internationalPrefix forKey:@"internationalPrefix"];
+ [coder encodeObject:self.preferredInternationalPrefix forKey:@"preferredInternationalPrefix"];
+ [coder encodeObject:self.nationalPrefix forKey:@"nationalPrefix"];
+ [coder encodeObject:self.preferredExtnPrefix forKey:@"preferredExtnPrefix"];
+ [coder encodeObject:self.nationalPrefixForParsing forKey:@"nationalPrefixForParsing"];
+ [coder encodeObject:self.nationalPrefixTransformRule forKey:@"nationalPrefixTransformRule"];
+ [coder encodeObject:[NSNumber numberWithBool:self.sameMobileAndFixedLinePattern] forKey:@"sameMobileAndFixedLinePattern"];
+ [coder encodeObject:self.numberFormats forKey:@"numberFormats"];
+ [coder encodeObject:self.intlNumberFormats forKey:@"intlNumberFormats"];
+ [coder encodeObject:[NSNumber numberWithBool:self.mainCountryForCode] forKey:@"mainCountryForCode"];
+ [coder encodeObject:self.leadingDigits forKey:@"leadingDigits"];
+ [coder encodeObject:[NSNumber numberWithBool:self.leadingZeroPossible] forKey:@"leadingZeroPossible"];
+}
+
+
+- (NSMutableArray*)numberFormatArrayFromData:(id)data
+{
+ NSMutableArray *resArray = [[NSMutableArray alloc] init];
+ if (data != nil && [data isKindOfClass:[NSArray class]])
+ {
+ for (id numFormat in data)
+ {
+ NBNumberFormat *newNumberFormat = [[NBNumberFormat alloc] initWithData:numFormat];
+ [resArray addObject:newNumberFormat];
+ }
+ }
+
+ return resArray;
+}
+
+/*
+- (NSString*)getNormalizedNationalPrefixFormattingRule
+{
+ NSString *replacedFormattingRule = [self.nationalPrefixFormattingRule stringByReplacingOccurrencesOfString:@"$NP" withString:self.nationalPrefix];
+ return replacedFormattingRule;
+}
+
+
+- (BOOL)sameMobileAndFixedLinePattern
+{
+ if ([self.mobile isEqual:self.fixedLine]) return YES;
+ return NO;
+}
+
+
+- (NBPhoneNumberDesc*)inheriteValues:(NBPhoneNumberDesc*)targetDesc
+{
+ if (targetDesc == nil)
+ {
+ targetDesc = [[NBPhoneNumberDesc alloc] init];
+ }
+
+ if (self.generalDesc != nil)
+ {
+ if (targetDesc.nationalNumberPattern == nil)
+ {
+ if (self.generalDesc.nationalNumberPattern != nil)
+ targetDesc.nationalNumberPattern = [self.generalDesc.nationalNumberPattern copy];
+ }
+
+ if (targetDesc.possibleNumberPattern == nil)
+ {
+ if (self.generalDesc.possibleNumberPattern != nil)
+ targetDesc.possibleNumberPattern = [self.generalDesc.possibleNumberPattern copy];
+ }
+
+ if (targetDesc.exampleNumber == nil)
+ {
+ if (self.generalDesc.exampleNumber != nil)
+ targetDesc.exampleNumber = [self.generalDesc.exampleNumber copy];
+ }
+ }
+
+ return targetDesc;
+}
+
+
+- (void)updateDescriptions
+{
+ self.fixedLine = [[self inheriteValues:self.fixedLine] copy];
+ self.mobile = [[self inheriteValues:self.mobile] copy];
+ self.tollFree = [[self inheriteValues:self.tollFree] copy];
+ self.premiumRate = [[self inheriteValues:self.premiumRate] copy];
+ self.sharedCost = [[self inheriteValues:self.sharedCost] copy];
+ self.personalNumber = [[self inheriteValues:self.personalNumber] copy];
+ self.voip = [[self inheriteValues:self.voip] copy];
+ self.pager = [[self inheriteValues:self.pager] copy];
+ self.uan = [[self inheriteValues:self.uan] copy];
+ self.emergency = [[self inheriteValues:self.emergency] copy];
+ self.voicemail = [[self inheriteValues:self.voicemail] copy];
+ self.noInternationalDialling = [[self inheriteValues:self.noInternationalDialling] copy];
+}
+
+
+- (void)setAttributes:(NSDictionary*)data
+{
+ NSString *attributeName = [data valueForKey:@"attributeName"];
+ id attributeContent = [data valueForKey:@"nodeContent"];
+
+ if ([attributeContent isKindOfClass:[NSString class]] && [attributeContent length] > 0)
+ attributeContent = [NBPhoneNumberManager stringByTrimming:attributeContent];
+
+ if (attributeName && [attributeName isKindOfClass:[NSString class]] && [attributeName length] > 0 && [attributeName isEqualToString:@"id"] &&
+ attributeContent && [attributeContent isKindOfClass:[NSString class]] && [attributeContent length] > 0)
+ {
+ [self setCodeID:attributeContent];
+ }
+ else if (attributeName && [attributeName isKindOfClass:[NSString class]] && [attributeName length] > 0 && attributeContent && [attributeContent isKindOfClass:[NSString class]] && [attributeContent length] > 0)
+ {
+ @try {
+ if ([[attributeContent lowercaseString] isEqualToString:@"true"])
+ {
+ [self setValue:[NSNumber numberWithBool:YES] forKey:attributeName];
+ }
+ else if ([[attributeContent lowercaseString] isEqualToString:@"false"])
+ {
+ [self setValue:[NSNumber numberWithBool:NO] forKey:attributeName];
+ }
+ else
+ {
+ [self setValue:attributeContent forKey:attributeName];
+ }
+ }
+ @catch (NSException *ex) {
+ NSLog(@"setAttributes setValue:%@ forKey:%@ error [%@]", attributeContent, attributeName, [attributeContent class]);
+ }
+ }
+}
+
+
+- (BOOL)setChilds:(id)data
+{
+ if (data && [data isKindOfClass:[NSDictionary class]])
+ {
+ NSString *nodeName = [data valueForKey:@"nodeName"];
+ id nodeContent = [data valueForKey:@"nodeContent"];
+
+ if ([nodeContent isKindOfClass:[NSString class]] && [nodeContent length] > 0)
+ nodeContent = [NBPhoneNumberManager stringByTrimming:nodeContent];
+
+ // [TYPE] PhoneNumberDesc
+ if ([nodeName isEqualToString:@"generalDesc"] || [nodeName isEqualToString:@"fixedLine"] || [nodeName isEqualToString:@"mobile"] || [nodeName isEqualToString:@"shortCode"] || [nodeName isEqualToString:@"emergency"] || [nodeName isEqualToString:@"voip"] || [nodeName isEqualToString:@"voicemail"] || [nodeName isEqualToString:@"uan"] || [nodeName isEqualToString:@"premiumRate"] || [nodeName isEqualToString:@"nationalNumberPattern"] || [nodeName isEqualToString:@"sharedCost"] || [nodeName isEqualToString:@"tollFree"] || [nodeName isEqualToString:@"noInternationalDialling"] || [nodeName isEqualToString:@"personalNumber"] || [nodeName isEqualToString:@"pager"] || [nodeName isEqualToString:@"areaCodeOptional"])
+ {
+ [self setNumberDescData:data];
+ return YES;
+ }
+ else if ([nodeName isEqualToString:@"availableFormats"])
+ {
+ [self setNumberFormatsData:data];
+ return YES;
+ }
+ else if ([nodeName isEqualToString:@"comment"] == NO && [nodeContent isKindOfClass:[NSString class]])
+ {
+ [self setValue:nodeContent forKey:nodeName];
+ return YES;
+ }
+ else if ([nodeName isEqualToString:@"comment"])
+ {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+
+- (void)setNumberFormatsData:(id)data
+{
+ NSArray *nodeChildArray = [data valueForKey:@"nodeChildArray"];
+
+ for (id childNumberFormat in nodeChildArray)
+ {
+ NSArray *nodeChildAttributeNumberFormatArray = [childNumberFormat valueForKey:@"nodeAttributeArray"];
+ NSArray *nodeChildNodeNumberFormatArray = [childNumberFormat valueForKey:@"nodeChildArray"];
+
+ NSString *nodeName = [childNumberFormat valueForKey:@"nodeName"];
+
+ if ([nodeName isEqualToString:@"numberFormat"])
+ {
+ NBNumberFormat *newNumberFormat = [[NBNumberFormat alloc] init];
+
+ for (id childAttribute in nodeChildAttributeNumberFormatArray)
+ {
+ NSString *childNodeName = [childAttribute valueForKey:@"attributeName"];
+ NSString *childNodeContent = nil;
+
+ if ([childNodeName isEqualToString:@"comment"])
+ {
+ continue;
+ }
+
+ childNodeContent = [NBPhoneNumberManager stringByTrimming:[childAttribute valueForKey:@"nodeContent"]];
+
+ @try {
+ [newNumberFormat setValue:childNodeContent forKey:childNodeName];
+ }
+ @catch (NSException *ex) {
+ NSLog(@"nodeChildAttributeArray setValue:%@ forKey:%@ error [%@] %@", childNodeContent, childNodeName, [childNodeContent class], childAttribute);
+ }
+ }
+
+ for (id childNode in nodeChildNodeNumberFormatArray)
+ {
+ NSString *childNodeName = [childNode valueForKey:@"nodeName"];
+ NSString *childNodeContent = nil;
+
+ if ([childNodeName isEqualToString:@"comment"])
+ {
+ continue;
+ }
+
+ childNodeContent = [NBPhoneNumberManager stringByTrimming:[childNode valueForKey:@"nodeContent"]];
+
+ @try {
+ if ([childNodeName isEqualToString:@"leadingDigits"])
+ {
+ [newNumberFormat.leadingDigitsPattern addObject:childNodeContent];
+ }
+ else
+ {
+ if ([[childNodeContent lowercaseString] isEqualToString:@"true"])
+ {
+ [newNumberFormat setValue:[NSNumber numberWithBool:YES] forKey:childNodeName];
+ }
+ else if ([[childNodeContent lowercaseString] isEqualToString:@"false"])
+ {
+ [newNumberFormat setValue:[NSNumber numberWithBool:NO] forKey:childNodeName];
+ }
+ else
+ {
+ [newNumberFormat setValue:childNodeContent forKey:childNodeName];
+ }
+ }
+ }
+ @catch (NSException *ex) {
+ NSLog(@"nodeChildArray setValue:%@ forKey:%@ error [%@] %@", childNodeContent, childNodeName, [childNodeContent class], childNode);
+ }
+ }
+ [self.numberFormats addObject:newNumberFormat];
+ }
+ else if ([nodeName isEqualToString:@"comment"] == NO)
+ {
+ NSLog(@"process ========== %@", childNumberFormat);
+ }
+ }
+}
+
+
+- (void)setNumberDescData:(id)data
+{
+ NSString *nodeName = [data valueForKey:@"nodeName"];
+ NSArray *nodeChildArray = [data valueForKey:@"nodeChildArray"];
+
+ NBPhoneNumberDesc *newNumberDesc = [[NBPhoneNumberDesc alloc] init];
+
+ for (id childNode in nodeChildArray)
+ {
+ NSString *childNodeName = [childNode valueForKey:@"nodeName"];
+ NSString *childNodeContent = [NBPhoneNumberManager stringByTrimming:[childNode valueForKey:@"nodeContent"]];
+
+ if ([childNodeName isEqualToString:@"comment"])
+ {
+ continue;
+ }
+
+ @try {
+ if (childNodeContent && childNodeContent.length > 0)
+ {
+ [newNumberDesc setValue:childNodeContent forKey:childNodeName];
+ }
+ }
+ @catch (NSException *ex) {
+ NSLog(@"setNumberDesc setValue:%@ forKey:%@ error [%@]", childNodeContent, childNodeName, [childNodeContent class]);
+ }
+ }
+
+ nodeName = [nodeName lowercaseString];
+
+ if ([nodeName isEqualToString:[@"generalDesc" lowercaseString]])
+ self.generalDesc = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"fixedLine" lowercaseString]])
+ self.fixedLine = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"mobile" lowercaseString]])
+ self.mobile = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"tollFree" lowercaseString]]) {
+ [self setTollFree:newNumberDesc];
+ }
+
+ if ([nodeName isEqualToString:[@"premiumRate" lowercaseString]])
+ self.premiumRate = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"sharedCost" lowercaseString]]) {
+ self.sharedCost = newNumberDesc;
+ }
+
+ if ([nodeName isEqualToString:[@"personalNumber" lowercaseString]])
+ self.personalNumber = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"voip" lowercaseString]])
+ self.voip = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"pager" lowercaseString]])
+ self.pager = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"uan" lowercaseString]])
+ self.uan = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"emergency" lowercaseString]])
+ self.emergency = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"voicemail" lowercaseString]])
+ self.voicemail = newNumberDesc;
+
+ if ([nodeName isEqualToString:[@"noInternationalDialling" lowercaseString]])
+ self.noInternationalDialling = newNumberDesc;
+
+ [self updateDescriptions];
+}
+*/
+
+@end
\ No newline at end of file
diff --git a/libPhoneNumber/NBPhoneNumber.h b/libPhoneNumber/NBPhoneNumber.h
new file mode 100644
index 0000000..dfa3b17
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumber.h
@@ -0,0 +1,25 @@
+//
+// NBPhoneNumber.h
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import <Foundation/Foundation.h>
+#import "NBPhoneNumberDefines.h"
+
+@interface NBPhoneNumber : NSObject <NSCopying>
+
+// from phonemetadata.pb.js
+/* 1 */ @property (nonatomic, strong, readwrite) NSNumber *countryCode;
+/* 2 */ @property (nonatomic, strong, readwrite) NSNumber *nationalNumber;
+/* 3 */ @property (nonatomic, strong, readwrite) NSString *extension;
+/* 4 */ @property (nonatomic, assign, readwrite) BOOL italianLeadingZero;
+/* 5 */ @property (nonatomic, strong, readwrite) NSString *rawInput;
+/* 6 */ @property (nonatomic, strong, readwrite) NSNumber *countryCodeSource;
+/* 7 */ @property (nonatomic, strong, readwrite) NSString *preferredDomesticCarrierCode;
+
+- (void)clearCountryCodeSource;
+- (NBECountryCodeSource)getCountryCodeSourceOrDefault;
+
+@end
\ No newline at end of file
diff --git a/libPhoneNumber/NBPhoneNumber.m b/libPhoneNumber/NBPhoneNumber.m
new file mode 100644
index 0000000..cd79761
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumber.m
@@ -0,0 +1,121 @@
+//
+// NBPhoneNumber.m
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import "NBPhoneNumber.h"
+#import "NBPhoneNumberDefines.h"
+
+@implementation NBPhoneNumber
+
+@synthesize countryCode, nationalNumber, extension, italianLeadingZero, rawInput, countryCodeSource, preferredDomesticCarrierCode;
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ self.countryCodeSource = nil;
+ self.italianLeadingZero = NO;
+ self.nationalNumber = @-1;
+ self.countryCode = @-1;
+ }
+
+ return self;
+}
+
+
+- (void)clearCountryCodeSource
+{
+ [self setCountryCodeSource:nil];
+}
+
+
+- (NBECountryCodeSource)getCountryCodeSourceOrDefault
+{
+ if (self.countryCodeSource == nil)
+ return NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN;
+
+ return [self.countryCodeSource intValue];
+}
+
+
+- (BOOL)isEqualToObject:(NBPhoneNumber*)otherObj
+{
+ return [self isEqual:otherObj];
+}
+
+
+- (NSUInteger)hash
+{
+ NSData *selfObject = [NSKeyedArchiver archivedDataWithRootObject:self];
+ return [selfObject hash];
+}
+
+
+- (BOOL)isEqual:(id)object
+{
+ if ([object isKindOfClass:[NBPhoneNumber class]] == NO)
+ return NO;
+
+ NBPhoneNumber *other = object;
+ return ([self.countryCode isEqualToNumber: other.countryCode]) && ([self.nationalNumber isEqualToNumber: other.nationalNumber]) &&
+ (self.italianLeadingZero == other.italianLeadingZero) &&
+ ((self.extension == nil && other.extension == nil) || [self.extension isEqualToString:other.extension]);
+}
+
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ NBPhoneNumber *phoneNumberCopy = [[NBPhoneNumber allocWithZone:zone] init];
+
+ phoneNumberCopy.countryCode = [self.countryCode copy];
+ phoneNumberCopy.nationalNumber = [self.nationalNumber copy];
+ phoneNumberCopy.extension = [self.extension copy];
+ phoneNumberCopy.italianLeadingZero = self.italianLeadingZero;
+ phoneNumberCopy.rawInput = [self.rawInput copy];
+ phoneNumberCopy.countryCodeSource = [self.countryCodeSource copy];
+ phoneNumberCopy.preferredDomesticCarrierCode = [self.preferredDomesticCarrierCode copy];
+
+ return phoneNumberCopy;
+}
+
+
+- (id)initWithCoder:(NSCoder*)coder
+{
+ if (self = [super init])
+ {
+ self.countryCode = [coder decodeObjectForKey:@"countryCode"];
+ self.nationalNumber = [coder decodeObjectForKey:@"nationalNumber"];
+ self.extension = [coder decodeObjectForKey:@"extension"];
+ self.italianLeadingZero = [[coder decodeObjectForKey:@"italianLeadingZero"] boolValue];
+ self.rawInput = [coder decodeObjectForKey:@"rawInput"];
+ self.countryCodeSource = [coder decodeObjectForKey:@"countryCodeSource"];
+ self.preferredDomesticCarrierCode = [coder decodeObjectForKey:@"preferredDomesticCarrierCode"];
+ }
+ return self;
+}
+
+
+- (void)encodeWithCoder:(NSCoder*)coder
+{
+ [coder encodeObject:self.countryCode forKey:@"countryCode"];
+ [coder encodeObject:self.nationalNumber forKey:@"nationalNumber"];
+ [coder encodeObject:self.extension forKey:@"extension"];
+ [coder encodeObject:[NSNumber numberWithBool:self.italianLeadingZero] forKey:@"italianLeadingZero"];
+ [coder encodeObject:self.rawInput forKey:@"rawInput"];
+ [coder encodeObject:self.countryCodeSource forKey:@"countryCodeSource"];
+ [coder encodeObject:self.preferredDomesticCarrierCode forKey:@"preferredDomesticCarrierCode"];
+}
+
+
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@" - countryCode[%@], nationalNumber[%@], extension[%@], italianLeadingZero[%@], rawInput[%@] countryCodeSource[%d] preferredDomesticCarrierCode[%@]", self.countryCode, self.nationalNumber, self.extension, self.italianLeadingZero?@"Y":@"N", self.rawInput, [self.countryCodeSource intValue], self.preferredDomesticCarrierCode];
+}
+
+@end
diff --git a/libPhoneNumber/NBPhoneNumberDefines.h b/libPhoneNumber/NBPhoneNumberDefines.h
new file mode 100644
index 0000000..f7c9bf8
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberDefines.h
@@ -0,0 +1,107 @@
+//
+// NBPhoneNumberDefines.h
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#ifndef libPhoneNumber_NBPhoneNumberDefines_h
+#define libPhoneNumber_NBPhoneNumberDefines_h
+
+#define NB_YES [NSNumber numberWithBool:YES]
+#define NB_NO [NSNumber numberWithBool:NO]
+
+#pragma mark - Enum -
+
+typedef enum {
+ NBEPhoneNumberFormatE164 = 0,
+ NBEPhoneNumberFormatINTERNATIONAL = 1,
+ NBEPhoneNumberFormatNATIONAL = 2,
+ NBEPhoneNumberFormatRFC3966 = 3
+} NBEPhoneNumberFormat;
+
+
+typedef enum {
+ NBEPhoneNumberTypeFIXED_LINE = 0,
+ NBEPhoneNumberTypeMOBILE = 1,
+ // In some regions (e.g. the USA), it is impossible to distinguish between
+ // fixed-line and mobile numbers by looking at the phone number itself.
+ NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE = 2,
+ // Freephone lines
+ NBEPhoneNumberTypeTOLL_FREE = 3,
+ NBEPhoneNumberTypePREMIUM_RATE = 4,
+ // The cost of this call is shared between the caller and the recipient, and
+ // is hence typically less than PREMIUM_RATE calls. See
+ // http://en.wikipedia.org/wiki/Shared_Cost_Service for more information.
+ NBEPhoneNumberTypeSHARED_COST = 5,
+ // Voice over IP numbers. This includes TSoIP (Telephony Service over IP).
+ NBEPhoneNumberTypeVOIP = 6,
+ // A personal number is associated with a particular person, and may be routed
+ // to either a MOBILE or FIXED_LINE number. Some more information can be found
+ // here = http://en.wikipedia.org/wiki/Personal_Numbers
+ NBEPhoneNumberTypePERSONAL_NUMBER = 7,
+ NBEPhoneNumberTypePAGER = 8,
+ // Used for 'Universal Access Numbers' or 'Company Numbers'. They may be
+ // further routed to specific offices, but allow one number to be used for a
+ // company.
+ NBEPhoneNumberTypeUAN = 9,
+ // Used for 'Voice Mail Access Numbers'.
+ NBEPhoneNumberTypeVOICEMAIL = 10,
+ // A phone number is of type UNKNOWN when it does not fit any of the known
+ // patterns for a specific region.
+ NBEPhoneNumberTypeUNKNOWN = -1
+} NBEPhoneNumberType;
+
+
+typedef enum {
+ NBEMatchTypeNOT_A_NUMBER = 0,
+ NBEMatchTypeNO_MATCH = 1,
+ NBEMatchTypeSHORT_NSN_MATCH = 2,
+ NBEMatchTypeNSN_MATCH = 3,
+ NBEMatchTypeEXACT_MATCH = 4
+} NBEMatchType;
+
+
+typedef enum {
+ NBEValidationResultIS_POSSIBLE = 0,
+ NBEValidationResultINVALID_COUNTRY_CODE = 1,
+ NBEValidationResultTOO_SHORT = 2,
+ NBEValidationResultTOO_LONG = 3
+} NBEValidationResult;
+
+
+typedef enum {
+ NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN = 1,
+ NBECountryCodeSourceFROM_NUMBER_WITH_IDD = 5,
+ NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN = 10,
+ NBECountryCodeSourceFROM_DEFAULT_COUNTRY = 20
+} NBECountryCodeSource;
+
+
+@interface NSArray (NBAdditions)
+
+- (id)safeObjectAtIndex:(NSUInteger)index;
+
+@end
+
+
+@implementation NSArray (NBAdditions)
+
+- (id)safeObjectAtIndex:(NSUInteger)index
+{
+ @synchronized(self)
+ {
+ if(index >= [self count]) return nil;
+
+ id res = [self objectAtIndex:index];
+
+ if (res == nil || (NSNull*)res == [NSNull null])
+ return nil;
+
+ return res;
+ }
+}
+
+@end
+
+#endif
diff --git a/libPhoneNumber/NBPhoneNumberDesc.h b/libPhoneNumber/NBPhoneNumberDesc.h
new file mode 100644
index 0000000..288727c
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberDesc.h
@@ -0,0 +1,19 @@
+//
+// NBPhoneNumberDesc.h
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NBPhoneNumberDesc : NSObject
+
+// from phonemetadata.pb.js
+/* 2 */ @property (nonatomic, strong, readwrite) NSString *nationalNumberPattern;
+/* 3 */ @property (nonatomic, strong, readwrite) NSString *possibleNumberPattern;
+/* 6 */ @property (nonatomic, strong, readwrite) NSString *exampleNumber;
+
+- (id)initWithData:(id)data;
+
+@end
diff --git a/libPhoneNumber/NBPhoneNumberDesc.m b/libPhoneNumber/NBPhoneNumberDesc.m
new file mode 100644
index 0000000..f698103
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberDesc.m
@@ -0,0 +1,90 @@
+//
+// NBPhoneNumberDesc.m
+// libPhoneNumber
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import "NBPhoneNumberDesc.h"
+#import "NBPhoneNumberDefines.h"
+
+@implementation NBPhoneNumberDesc
+
+@synthesize nationalNumberPattern, possibleNumberPattern, exampleNumber;
+
+
+- (id)initWithData:(id)data
+{
+ self = [self init];
+
+ if (self && data != nil && [data isKindOfClass:[NSArray class]])
+ {
+ /* 2 */ self.nationalNumberPattern = [data safeObjectAtIndex:2];
+ /* 3 */ self.possibleNumberPattern = [data safeObjectAtIndex:3];
+ /* 6 */ self.exampleNumber = [data safeObjectAtIndex:6];
+ }
+
+ return self;
+}
+
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ }
+
+ return self;
+}
+
+
+- (id)initWithCoder:(NSCoder*)coder
+{
+ if (self = [super init])
+ {
+ self.nationalNumberPattern = [coder decodeObjectForKey:@"nationalNumberPattern"];
+ self.possibleNumberPattern = [coder decodeObjectForKey:@"possibleNumberPattern"];
+ self.exampleNumber = [coder decodeObjectForKey:@"exampleNumber"];
+ }
+ return self;
+}
+
+
+- (void)encodeWithCoder:(NSCoder*)coder
+{
+ [coder encodeObject:self.nationalNumberPattern forKey:@"nationalNumberPattern"];
+ [coder encodeObject:self.possibleNumberPattern forKey:@"possibleNumberPattern"];
+ [coder encodeObject:self.exampleNumber forKey:@"exampleNumber"];
+}
+
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"nationalNumberPattern[%@] possibleNumberPattern[%@] exampleNumber[%@]", self.nationalNumberPattern, self.possibleNumberPattern, self.exampleNumber];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ NBPhoneNumberDesc *phoneDescCopy = [[NBPhoneNumberDesc allocWithZone:zone] init];
+
+ phoneDescCopy.nationalNumberPattern = [self.nationalNumberPattern copy];
+ phoneDescCopy.possibleNumberPattern = [self.possibleNumberPattern copy];
+ phoneDescCopy.exampleNumber = [self.exampleNumber copy];
+
+ return phoneDescCopy;
+}
+
+- (BOOL)isEqual:(id)object
+{
+ if ([object isKindOfClass:[NBPhoneNumberDesc class]] == NO)
+ return NO;
+
+ NBPhoneNumberDesc *other = object;
+ return [self.nationalNumberPattern isEqual:other.nationalNumberPattern] &&
+ [self.possibleNumberPattern isEqual:other.possibleNumberPattern] &&
+ [self.exampleNumber isEqual:other.exampleNumber];
+}
+
+@end
diff --git a/libPhoneNumber/NBPhoneNumberMetadata.plist b/libPhoneNumber/NBPhoneNumberMetadata.plist
new file mode 100644
index 0000000..8cd2d03
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberMetadata.plist
Binary files differ
diff --git a/libPhoneNumber/NBPhoneNumberUtil.h b/libPhoneNumber/NBPhoneNumberUtil.h
new file mode 100644
index 0000000..e9e13ec
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberUtil.h
@@ -0,0 +1,108 @@
+//
+// NBPhoneNumberUtil.h
+// Band
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import <Foundation/Foundation.h>
+#import "NBPhoneNumberDefines.h"
+
+@class NBPhoneMetaData, NBPhoneNumber;
+
+@interface NBPhoneNumberUtil : NSObject
+
++ (NBPhoneNumberUtil*)sharedInstance;
++ (NBPhoneNumberUtil*)sharedInstanceForTest;
+
+// regular expressions
+- (NSArray*)matchesByRegex:(NSString*)sourceString regex:(NSString*)pattern;
+- (NSArray*)matchedStringByRegex:(NSString*)sourceString regex:(NSString*)pattern;
+- (NSString*)replaceStringByRegex:(NSString*)sourceString regex:(NSString*)pattern withTemplate:(NSString*)templateString;
+- (NSInteger)stringPositionByRegex:(NSString*)sourceString regex:(NSString*)pattern;
+
+
++ (NSString*)stringByTrimming:(NSString*)aString;
+
+//- (NSString*)numbersOnly:(NSString*)phoneNumber;
+- (NSArray*)regionCodeFromCountryCode:(NSNumber *)countryCodeNumber;
+- (NSString*)countryCodeFromRegionCode:(NSString*)regionCode;
+
+
+// libPhoneNumber Util functions
+- (NSString*)convertAlphaCharactersInNumber:(NSString*)number;
+
+- (NSString*)normalizePhoneNumber:(NSString*)phoneNumber;
+- (NSString*)normalizeDigitsOnly:(NSString*)number;
+
+- (BOOL)isNumberGeographical:(NBPhoneNumber*)phoneNumber;
+
+- (NSString*)extractPossibleNumber:(NSString*)phoneNumber;
+- (NSNumber*)extractCountryCode:(NSString*)fullNumber nationalNumber:(NSString**)nationalNumber;
+
+- (NSString*)getNddPrefixForRegion:(NSString*)regionCode stripNonDigits:(BOOL)stripNonDigits;
+- (NSString*)getNationalSignificantNumber:(NBPhoneNumber*)phoneNumber;
+
+- (NBEPhoneNumberType)getNumberType:(NBPhoneNumber*)phoneNumber;
+
+- (NSNumber*)getCountryCodeForRegion:(NSString*)regionCode;
+
+- (NSString*)getRegionCodeForCountryCode:(NSNumber*)countryCallingCode;
+- (NSArray*)getRegionCodesForCountryCode:(NSNumber*)countryCallingCode;
+- (NSString*)getRegionCodeForNumber:(NBPhoneNumber*)phoneNumber;
+
+- (NBPhoneNumber*)getExampleNumber:(NSString*)regionCode error:(NSError**)error;
+- (NBPhoneNumber*)getExampleNumberForType:(NSString*)regionCode type:(NBEPhoneNumberType)type error:(NSError**)error;
+- (NBPhoneNumber*)getExampleNumberForNonGeoEntity:(NSNumber*)countryCallingCode error:(NSError**)error;
+
+- (NBPhoneMetaData*)getMetadataForNonGeographicalRegion:(NSNumber*)countryCallingCode;
+- (NBPhoneMetaData*)getMetadataForRegion:(NSString*)regionCode;
+
+- (BOOL)canBeInternationallyDialled:(NBPhoneNumber*)number error:(NSError**)error;
+
+- (BOOL)truncateTooLongNumber:(NBPhoneNumber*)number error:(NSError**)error;
+
+- (BOOL)isValidNumber:(NBPhoneNumber*)number;
+- (BOOL)isViablePhoneNumber:(NSString*)phoneNumber;
+- (BOOL)isAlphaNumber:(NSString*)number;
+- (BOOL)isValidNumberForRegion:(NBPhoneNumber*)number regionCode:(NSString*)regionCode;
+- (BOOL)isNANPACountry:(NSString*)regionCode;
+- (BOOL)isLeadingZeroPossible:(NSNumber*)countryCallingCode;
+
+- (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber*)number error:(NSError**)error;
+
+- (BOOL)isPossibleNumber:(NBPhoneNumber*)number error:(NSError**)error;
+- (BOOL)isPossibleNumberString:(NSString*)number regionDialingFrom:(NSString*)regionDialingFrom error:(NSError**)error;
+
+- (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn error:(NSError**)error;
+
+- (NSUInteger)getLengthOfGeographicalAreaCode:(NBPhoneNumber*)phoneNumber error:(NSError**)error;
+- (NSUInteger)getLengthOfNationalDestinationCode:(NBPhoneNumber*)phoneNumber error:(NSError**)error;
+
+- (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString**)numberStr metadata:(NBPhoneMetaData*)metadata carrierCode:(NSString**)carrierCode error:(NSError**)error;
+- (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString**)numberStr possibleIddPrefix:(NSString*)possibleIddPrefix error:(NSError**)error;
+- (NSNumber*)maybeExtractCountryCode:(NSString*)number metadata:(NBPhoneMetaData*)defaultRegionMetadata
+ nationalNumber:(NSString**)nationalNumber keepRawInput:(BOOL)keepRawInput phoneNumber:(NBPhoneNumber**)phoneNumber error:(NSError**)error;
+
+- (NBPhoneNumber*)parse:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error;
+- (NBPhoneNumber*)parseAndKeepRawInput:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error;
+- (NBPhoneNumber*)parseWithPhoneCarrierRegion:(NSString*)numberToParse error:(NSError**)error;
+
+- (NSString*)format:(NBPhoneNumber*)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat error:(NSError**)error;
+- (NSString*)formatByPattern:(NBPhoneNumber*)number numberFormat:(NBEPhoneNumberFormat)numberFormat userDefinedFormats:(NSArray*)userDefinedFormats error:(NSError**)error;
+- (NSString*)formatNumberForMobileDialing:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom withFormatting:(BOOL)withFormatting error:(NSError**)error;
+- (NSString*)formatOutOfCountryCallingNumber:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError**)error;
+- (NSString*)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError**)error;
+- (NSString*)formatNationalNumberWithCarrierCode:(NBPhoneNumber*)number carrierCode:(NSString*)carrierCode error:(NSError**)error;
+- (NSString*)formatInOriginalFormat:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError**)error;
+- (NSString*)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber*)number fallbackCarrierCode:(NSString*)fallbackCarrierCode error:(NSError**)error;
+
+- (BOOL)formattingRuleHasFirstGroupOnly:(NSString*)nationalPrefixFormattingRule;
+
+@property (nonatomic, readonly) NSString *VALID_PUNCTUATION;
+@property (nonatomic, readonly) NSString *VALID_DIGITS_STRING;
+@property (nonatomic, readonly) NSString *PLUS_CHARS_;
+@property (nonatomic, readonly) NSString *REGION_CODE_FOR_NON_GEO_ENTITY;
+@property (nonatomic, readonly) NSDictionary *DIGIT_MAPPINGS;
+
+@end
diff --git a/libPhoneNumber/NBPhoneNumberUtil.m b/libPhoneNumber/NBPhoneNumberUtil.m
new file mode 100644
index 0000000..bfb95c9
--- /dev/null
+++ b/libPhoneNumber/NBPhoneNumberUtil.m
@@ -0,0 +1,4305 @@
+//
+// NBPhoneNumberUtil.m
+// Band
+//
+// Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com)
+//
+
+#import "NBPhoneNumberUtil.h"
+#import "NBPhoneNumber.h"
+#import "NBNumberFormat.h"
+#import "NBPhoneNumberDesc.h"
+#import "NBPhoneMetaData.h"
+#import "math.h"
+
+#import <CoreTelephony/CTTelephonyNetworkInfo.h>
+#import <CoreTelephony/CTCarrier.h>
+
+
+#pragma mark - Static Int variables -
+const static NSUInteger NANPA_COUNTRY_CODE_ = 1;
+const static NSUInteger MIN_LENGTH_FOR_NSN_ = 2;
+const static NSUInteger MAX_LENGTH_FOR_NSN_ = 16;
+const static NSUInteger MAX_LENGTH_COUNTRY_CODE_ = 3;
+const static NSUInteger MAX_INPUT_STRING_LENGTH_ = 250;
+
+
+#pragma mark - Static String variables -
+NSString *INVALID_COUNTRY_CODE_STR = @"Invalid country calling code";
+NSString *NOT_A_NUMBER_STR = @"The string supplied did not seem to be a phone number";
+NSString *TOO_SHORT_AFTER_IDD_STR = @"Phone number too short after IDD";
+NSString *TOO_SHORT_NSN_STR = @"The string supplied is too short to be a phone number";
+NSString *TOO_LONG_STR = @"The string supplied is too long to be a phone number";
+
+NSString *UNKNOWN_REGION_ = @"ZZ";
+NSString *COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX_ = @"3";
+NSString *PLUS_SIGN = @"+";
+NSString *STAR_SIGN_ = @"*";
+NSString *RFC3966_EXTN_PREFIX_ = @";ext=";
+NSString *RFC3966_PREFIX_ = @"tel:";
+NSString *RFC3966_PHONE_CONTEXT_ = @";phone-context=";
+NSString *RFC3966_ISDN_SUBADDRESS_ = @";isub=";
+NSString *DEFAULT_EXTN_PREFIX_ = @" ext. ";
+NSString *VALID_ALPHA_ = @"A-Za-z";
+
+#pragma mark - Static regular expression strings -
+NSString *NON_DIGITS_PATTERN_ = @"\\D+";
+NSString *CC_PATTERN_ = @"\\$CC";
+NSString *FIRST_GROUP_PATTERN_ = @"(\\$\\d)";
+NSString *FIRST_GROUP_ONLY_PREFIX_PATTERN_ = @"^\\(?\\$1\\)?";
+NSString *NP_PATTERN_ = @"\\$NP";
+NSString *FG_PATTERN_ = @"\\$FG";
+NSString *VALID_ALPHA_PHONE_PATTERN_STRING = @"(?:.*?[A-Za-z]){3}.*";
+NSString *UNIQUE_INTERNATIONAL_PREFIX_ = @"[\\d]+(?:[~\\u2053\\u223C\\uFF5E][\\d]+)?";
+
+
+#pragma mark - NBPhoneNumberUtil interface -
+@interface NBPhoneNumberUtil ()
+
+/*
+ Terminologies
+ - Country Number (CN) = Country code for i18n calling
+ - Country Code (CC) : ISO country codes (2 chars)
+ Ref. site (countrycode.org)
+ */
+@property (nonatomic, readonly) NSDictionary *coreMetaData;
+@property (nonatomic, readonly) NSRegularExpression *PLUS_CHARS_PATTERN, *CAPTURING_DIGIT_PATTERN, *VALID_ALPHA_PHONE_PATTERN_;
+@property (nonatomic, readonly) NSString *LEADING_PLUS_CHARS_PATTERN_, *EXTN_PATTERN_, *SEPARATOR_PATTERN_, *VALID_PHONE_NUMBER_PATTERN_, *VALID_START_CHAR_PATTERN_, *UNWANTED_END_CHAR_PATTERN_, *SECOND_NUMBER_START_PATTERN_;
+
+@property (nonatomic, readonly) NSDictionary *ALPHA_MAPPINGS_, *ALL_NORMALIZATION_MAPPINGS_, *DIALLABLE_CHAR_MAPPINGS_, *ALL_PLUS_NUMBER_GROUPING_SYMBOLS_;
+
+@property (nonatomic, strong, readwrite) NSMutableDictionary *mapCCode2CN;
+@property (nonatomic, strong, readwrite) NSMutableDictionary *mapCN2CCode;
+
+@property (nonatomic, strong, readwrite) NSMutableDictionary *i18nNumberFormat;
+@property (nonatomic, strong, readwrite) NSMutableDictionary *i18nPhoneNumberDesc;
+@property (nonatomic, strong, readwrite) NSMutableDictionary *i18nPhoneMetadata;
+
+@end
+
+
+@implementation NBPhoneNumberUtil
+
++ (NBPhoneNumberUtil*)sharedInstance
+{
+ static NBPhoneNumberUtil *sharedOnceInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{ sharedOnceInstance = [[self alloc] init]; });
+ return sharedOnceInstance;
+}
+
+
++ (NBPhoneNumberUtil*)sharedInstanceForTest
+{
+ static NBPhoneNumberUtil *sharedOnceInstanceForTest = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{ sharedOnceInstanceForTest = [[self alloc] initForTest]; });
+ return sharedOnceInstanceForTest;
+}
+
+
++ (NSString*)stringByTrimming:(NSString*)aString
+{
+ NSString *aRes = @"";
+ NSArray *newlines = [aString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
+ for (NSString *line in newlines)
+ {
+ NSString *performedString = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+
+ if (performedString.length > 0)
+ aRes = [aRes stringByAppendingString:performedString];
+ }
+
+ if (newlines.count <= 0)
+ return aString;
+
+ return aRes;
+}
+
+
+#pragma mark - Regular expression Utilities -
+- (BOOL)hasValue:(NSString*)string
+{
+ if (string == nil || [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length <= 0)
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+
+- (NSMutableArray*)componentsSeparatedByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ NSString *replacedString = [self replaceStringByRegex:sourceString regex:pattern withTemplate:@"<SEP>"];
+ NSMutableArray *resArray = [[replacedString componentsSeparatedByString:@"<SEP>"] mutableCopy];
+ [resArray removeObject:@""];
+ return resArray;
+}
+
+
+- (NSInteger)stringPositionByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ if (sourceString == nil || sourceString.length <= 0 || pattern == nil || pattern.length <= 0) {
+ return -1;
+ }
+
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+
+ NSInteger foundPosition = -1;
+
+ if (matches.count > 0)
+ {
+ NSTextCheckingResult *match = [matches objectAtIndex:0];
+ return match.range.location;
+ }
+
+ return foundPosition;
+}
+
+
+- (NSInteger)indexOfStringByString:(NSString*)sourceString target:(NSString*)targetString
+{
+ NSRange finded = [sourceString rangeOfString:targetString];
+ if (finded.location != NSNotFound)
+ {
+ return finded.location;
+ }
+
+ return -1;
+}
+
+
+- (NSString*)replaceFirstStringByRegex:(NSString*)sourceString regex:(NSString*)pattern withTemplate:(NSString*)templateString
+{
+ NSString *replacementResult = [sourceString copy];
+ NSError *error = nil;
+
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSRange replaceRange = [currentPattern rangeOfFirstMatchInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+
+ if (replaceRange.location != NSNotFound)
+ {
+ replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy] options:0
+ range:replaceRange
+ withTemplate:templateString];
+ }
+
+ return replacementResult;
+}
+
+
+- (NSString*)replaceStringByRegex:(NSString*)sourceString regex:(NSString*)pattern withTemplate:(NSString*)templateString
+{
+ NSString *replacementResult = [sourceString copy];
+ NSError *error = nil;
+
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+
+ if ([matches count] == 1)
+ {
+ NSRange replaceRange = [currentPattern rangeOfFirstMatchInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+
+ if (replaceRange.location != NSNotFound)
+ {
+ replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy] options:0
+ range:replaceRange
+ withTemplate:templateString];
+ }
+ return replacementResult;
+ }
+
+ if ([matches count] > 1)
+ {
+ replacementResult = [currentPattern stringByReplacingMatchesInString:[replacementResult mutableCopy] options:0
+ range:NSMakeRange(0, sourceString.length) withTemplate:templateString];
+ return replacementResult;
+ }
+
+ return replacementResult;
+}
+
+
+- (NSTextCheckingResult*)matcheFirstByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+ if ([matches count] > 0)
+ return [matches objectAtIndex:0];
+ return nil;
+}
+
+
+- (NSArray*)matchesByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+ return matches;
+}
+
+
+- (NSArray*)matchedStringByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ NSArray *matches = [self matchesByRegex:sourceString regex:pattern];
+ NSMutableArray *matchString = [[NSMutableArray alloc] init];
+
+ for (NSTextCheckingResult *match in matches)
+ {
+ NSString *curString = [sourceString substringWithRange:match.range];
+ [matchString addObject:curString];
+ }
+
+ return matchString;
+}
+
+
+- (BOOL)isStartingStringByRegex:(NSString*)sourceString regex:(NSString*)pattern
+{
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
+ NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)];
+
+ for (NSTextCheckingResult *match in matches)
+ {
+ if (match.range.location == 0)
+ {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+
+- (NSString*)stringByReplacingOccurrencesString:(NSString *)sourceString withMap:(NSDictionary *)dicMap removeNonMatches:(BOOL)bRemove
+{
+ NSMutableString *targetString = [[NSMutableString alloc] initWithString:@""];
+
+ for(NSUInteger i=0; i<sourceString.length; i++)
+ {
+ unichar oneChar = [sourceString characterAtIndex:i];
+ NSString *keyString = [NSString stringWithCharacters:&oneChar length:1];
+ NSString *mappedValue = [dicMap objectForKey:keyString];
+ if (mappedValue != nil)
+ {
+ [targetString appendString:mappedValue];
+ }
+ else
+ {
+ if (bRemove == NO)
+ {
+ [targetString appendString:keyString];
+ }
+ }
+ }
+
+ return targetString;
+}
+
+
+- (BOOL)isNaN:(NSString*)sourceString
+{
+ NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
+ NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:sourceString];
+ BOOL hasNumberOnly = [alphaNums isSupersetOfSet:inStringSet];
+
+ return !hasNumberOnly;
+}
+
+/*
+ - (NSString*)numbersOnly:(NSString*)phoneNumber
+ {
+ NSMutableString *strippedString = [NSMutableString stringWithCapacity:phoneNumber.length];
+
+ NSScanner *scanner = [NSScanner scannerWithString:phoneNumber];
+ NSCharacterSet *numbers = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
+
+ while ([scanner isAtEnd] == NO) {
+ NSString *buffer;
+ if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) {
+ [strippedString appendString:buffer];
+
+ } else {
+ [scanner setScanLocation:([scanner scanLocation] + 1)];
+ }
+ }
+
+ return strippedString;
+ }
+ */
+
+
+- (NSString*)getNationalSignificantNumber:(NBPhoneNumber*)phoneNumber
+{
+ if (phoneNumber.italianLeadingZero)
+ {
+ return [NSString stringWithFormat:@"0%@", phoneNumber.nationalNumber];
+ }
+
+ return [NSString stringWithFormat:@"%@", phoneNumber.nationalNumber];
+}
+
+
+- (NSArray*)regionCodeFromCountryCode:(NSNumber *)countryCodeNumber
+{
+ if (self.mapCN2CCode == nil || [self.mapCN2CCode count] <= 0)
+ {
+ return nil;
+ }
+
+ id res = [self.mapCN2CCode objectForKey:[NSString stringWithFormat:@"%@", countryCodeNumber]];
+
+ if (res && [res isKindOfClass:[NSArray class]] && [((NSArray*)res) count] > 0)
+ {
+ return res;
+ }
+
+ return nil;
+}
+
+
+- (NSString*)countryCodeFromRegionCode:(NSString*)regionCode
+{
+ if (self.mapCCode2CN == nil || [self.mapCCode2CN count] <= 0)
+ {
+ return nil;
+ }
+
+ id res = [self.mapCCode2CN objectForKey:regionCode];
+
+ if (res)
+ {
+ return res;
+ }
+
+ return nil;
+}
+
+
+#pragma mark - Initializations -
+- (id)init
+{
+ self = [super init];
+ if (self)
+ {
+ [self initRegularExpressionSet];
+ [self initNormalizationMappings];
+
+ NSDictionary *resData = [self loadMetadata:@"NBPhoneNumberMetadata"];
+ _coreMetaData = [resData objectForKey:@"countryToMetadata"];
+ _mapCN2CCode = [resData objectForKey:@"countryCodeToRegionCodeMap"];
+
+ [self initCC2CN];
+ }
+
+ return self;
+}
+
+
+- (id)initForTest
+{
+ self = [super init];
+ if (self)
+ {
+ [self initRegularExpressionSet];
+ [self initNormalizationMappings];
+
+ NSDictionary *resData = [self loadMetadata:@"NBPhoneNumberMetadataForTesting"];
+ _coreMetaData = [resData objectForKey:@"countryToMetadata"];
+ _mapCN2CCode = [resData objectForKey:@"countryCodeToRegionCodeMap"];
+
+ [self initCC2CN];
+ }
+
+ return self;
+}
+
+
+- (NSDictionary*)loadMetadata:(NSString*)name
+{
+ NSDictionary *unarchiveData = nil;
+
+ @try {
+ NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:@"plist"];
+ NSData *fileData = [NSData dataWithContentsOfFile:filePath];
+ unarchiveData = [NSKeyedUnarchiver unarchiveObjectWithData:fileData];
+ }
+ @catch (NSException *exception) {
+ return unarchiveData;
+ }
+
+ return unarchiveData;
+}
+
+/*
+ - (NSDictionary*)generateMetadata:(id)metaClass
+ {
+ NSMutableDictionary *resMedata = [[NSMutableDictionary alloc] init];
+ NSDictionary *srcMedata = nil;
+
+ if ([metaClass isKindOfClass:[NBPhoneNumberMetadataForTesting class]])
+ {
+ srcMedata = ((NBPhoneNumberMetadataForTesting*)metaClass).metadata;
+ }
+ else if ([metaClass isKindOfClass:[NBPhoneNumberMetadata class]])
+ {
+ srcMedata = ((NBPhoneNumberMetadata*)metaClass).metadata;
+ }
+ else
+ {
+ return resMedata;
+ }
+
+ NSDictionary *countryCodeToRegionCodeMap = [srcMedata objectForKey:@"countryCodeToRegionCodeMap"];
+ NSDictionary *countryToMetadata = [srcMedata objectForKey:@"countryToMetadata"];
+ NSLog(@"- countryCodeToRegionCodeMap count [%d]", [countryCodeToRegionCodeMap count]);
+ NSLog(@"- countryToMetadata count [%d]", [countryToMetadata count]);
+
+ NSMutableDictionary *genetatedMetaData = [[NSMutableDictionary alloc] init];
+
+ for (id key in [countryToMetadata allKeys])
+ {
+ id metaData = [countryToMetadata objectForKey:key];
+
+ NBPhoneMetaData *newMetaData = [[NBPhoneMetaData alloc] init];
+ [newMetaData buildData:metaData];
+
+ [genetatedMetaData setObject:newMetaData forKey:key];
+ }
+
+ [resMedata setObject:countryCodeToRegionCodeMap forKey:@"countryCodeToRegionCodeMap"];
+ [resMedata setObject:genetatedMetaData forKey:@"countryToMetadata"];
+
+ return resMedata;
+ }
+ */
+
+
+- (void)initRegularExpressionSet
+{
+ _VALID_PUNCTUATION = @"-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~";
+ _VALID_DIGITS_STRING = @"0-90-9٠-٩۰-۹";
+ _PLUS_CHARS_ = @"++";
+ _REGION_CODE_FOR_NON_GEO_ENTITY = @"001";
+
+ NSString *EXTN_PATTERNS_FOR_PARSING_ = @"(?:;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})#)$";
+
+ NSError *error = nil;
+
+ _PLUS_CHARS_PATTERN =
+ [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"[%@]+", _PLUS_CHARS_] options:0 error:&error];
+
+ _LEADING_PLUS_CHARS_PATTERN_ = [NSString stringWithFormat:@"^[%@]+", _PLUS_CHARS_];
+
+ _CAPTURING_DIGIT_PATTERN =
+ [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"([%@])", _VALID_DIGITS_STRING] options:0 error:&error];
+
+ _VALID_START_CHAR_PATTERN_ = [NSString stringWithFormat:@"[%@%@]", _PLUS_CHARS_, _VALID_DIGITS_STRING];
+
+ _SECOND_NUMBER_START_PATTERN_ = @"[\\\\\\/] *x";
+
+ _VALID_ALPHA_PHONE_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:VALID_ALPHA_PHONE_PATTERN_STRING options:0 error:&error];
+
+ _UNWANTED_END_CHAR_PATTERN_ = [NSString stringWithFormat:@"[^%@%@#]+$", _VALID_DIGITS_STRING, VALID_ALPHA_];
+
+ _EXTN_PATTERN_ = [NSString stringWithFormat:@"(?:%@)$", EXTN_PATTERNS_FOR_PARSING_];
+
+
+ _SEPARATOR_PATTERN_ = [NSString stringWithFormat:@"[%@]+", _VALID_PUNCTUATION];
+
+ _VALID_PHONE_NUMBER_PATTERN_ = @"^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~*]*[0-90-9٠-٩۰-۹]){3,}[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~*A-Za-z0-90-9٠-٩۰-۹]*(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xx##~~]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)?$";
+}
+
+
+- (void)dealloc
+{
+ [self clearCC2CN];
+ [self clearCN2CC];
+}
+
+
+- (void)clearCC2CN
+{
+ if (self.mapCCode2CN != nil)
+ {
+ [self.mapCCode2CN removeAllObjects];
+ self.mapCCode2CN = nil;
+ }
+}
+
+
+- (void)clearCN2CC
+{
+ if (self.mapCN2CCode != nil)
+ {
+ [self.mapCN2CCode removeAllObjects];
+ self.mapCN2CCode = nil;
+ }
+}
+
+
+- (void)initNormalizationMappings
+{
+ _DIGIT_MAPPINGS = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
+ // Fullwidth digit 0 to 9
+ @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3", @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16", @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
+ // Arabic-indic digit 0 to 9
+ @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3", @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666", @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
+ // Eastern-Arabic digit 0 to 9
+ @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3", @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6", @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9", nil];
+
+ _DIALLABLE_CHAR_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
+ @"+", @"+", @"*", @"*", nil];
+
+ _ALPHA_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"2", @"A", @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F", @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J",
+ @"5", @"K", @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P", @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T",
+ @"8", @"U", @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z", nil];
+
+ _ALL_NORMALIZATION_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
+ // Fullwidth digit 0 to 9
+ @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3", @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16", @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
+ // Arabic-indic digit 0 to 9
+ @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3", @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666", @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
+ // Eastern-Arabic digit 0 to 9
+ @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3", @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6", @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9",
+ @"2", @"A", @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F", @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J",
+ @"5", @"K", @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P", @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T",
+ @"8", @"U", @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z", nil];
+
+ _ALL_PLUS_NUMBER_GROUPING_SYMBOLS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
+ @"A", @"A", @"B", @"B", @"C", @"C", @"D", @"D", @"E", @"E", @"F", @"F", @"G", @"G", @"H", @"H", @"I", @"I", @"J", @"J",
+ @"K", @"K", @"L", @"L", @"M", @"M", @"N", @"N", @"O", @"O", @"P", @"P", @"Q", @"Q", @"R", @"R", @"S", @"S", @"T", @"T",
+ @"U", @"U", @"V", @"V", @"W", @"W", @"X", @"X", @"Y", @"Y", @"Z", @"Z", @"A", @"a", @"B", @"b", @"C", @"c", @"D", @"d",
+ @"E", @"e", @"F", @"f", @"G", @"g", @"H", @"h", @"I", @"i", @"J", @"j", @"K", @"k", @"L", @"l", @"M", @"m", @"N", @"n",
+ @"O", @"o", @"P", @"p", @"Q", @"q", @"R", @"r", @"S", @"s", @"T", @"t", @"U", @"u", @"V", @"v", @"W", @"w", @"X", @"x",
+ @"Y", @"y", @"Z", @"z", @"-", @"-", @"-", @"\uFF0D", @"-", @"\u2010", @"-", @"\u2011", @"-", @"\u2012", @"-", @"\u2013", @"-", @"\u2014", @"-", @"\u2015",
+ @"-", @"\u2212", @"/", @"/", @"/", @"\uFF0F", @" ", @" ", @" ", @"\u3000", @" ", @"\u2060", @".", @".", @".", @"\uFF0E", nil];
+
+}
+
+
+- (void)initCC2CN
+{
+ [self clearCC2CN];
+ _mapCCode2CN = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"1", @"US", @"1", @"AG", @"1", @"AI", @"1", @"AS", @"1", @"BB", @"1", @"BM", @"1", @"BS", @"1", @"CA", @"1", @"DM", @"1", @"DO",
+ @"1", @"GD", @"1", @"GU", @"1", @"JM", @"1", @"KN", @"1", @"KY", @"1", @"LC", @"1", @"MP", @"1", @"MS", @"1", @"PR", @"1", @"SX",
+ @"1", @"TC", @"1", @"TT", @"1", @"VC", @"1", @"VG", @"1", @"VI", @"7", @"RU", @"7", @"KZ",
+ @"20", @"EG", @"27", @"ZA",
+ @"30", @"GR", @"31", @"NL", @"32", @"BE", @"33", @"FR", @"34", @"ES", @"36", @"HU", @"39", @"IT",
+ @"40", @"RO", @"41", @"CH", @"43", @"AT", @"44", @"GB", @"44", @"GG", @"44", @"IM", @"44", @"JE", @"45", @"DK", @"46", @"SE", @"47", @"NO", @"47", @"SJ", @"48", @"PL", @"49", @"DE",
+ @"51", @"PE", @"52", @"MX", @"53", @"CU", @"54", @"AR", @"55", @"BR", @"56", @"CL", @"57", @"CO", @"58", @"VE",
+ @"60", @"MY", @"61", @"AU", @"61", @"CC", @"61", @"CX", @"62", @"ID", @"63", @"PH", @"64", @"NZ", @"65", @"SG", @"66", @"TH",
+ @"81", @"JP", @"82", @"KR", @"84", @"VN", @"86", @"CN",
+ @"90", @"TR", @"91", @"IN", @"92", @"PK", @"93", @"AF", @"94", @"LK", @"95", @"MM", @"98", @"IR",
+ @"211", @"SS", @"212", @"MA", @"212", @"EH", @"213", @"DZ", @"216", @"TN", @"218", @"LY",
+ @"220", @"GM", @"221", @"SN", @"222", @"MR", @"223", @"ML", @"224", @"GN", @"225", @"CI", @"226", @"BF", @"227", @"NE", @"228", @"TG", @"229", @"BJ",
+ @"230", @"MU", @"231", @"LR", @"232", @"SL", @"233", @"GH", @"234", @"NG", @"235", @"TD", @"236", @"CF", @"237", @"CM", @"238", @"CV", @"239", @"ST",
+ @"240", @"GQ", @"241", @"GA", @"242", @"CG", @"243", @"CD", @"244", @"AO", @"245", @"GW", @"246", @"IO", @"247", @"AC", @"248", @"SC", @"249", @"SD",
+ @"250", @"RW", @"251", @"ET", @"252", @"SO", @"253", @"DJ", @"254", @"KE", @"255", @"TZ", @"256", @"UG", @"257", @"BI", @"258", @"MZ",
+ @"260", @"ZM", @"261", @"MG", @"262", @"RE", @"262", @"YT", @"263", @"ZW", @"264", @"NA", @"265", @"MW", @"266", @"LS", @"267", @"BW", @"268", @"SZ", @"269", @"KM",
+ @"290", @"SH", @"291", @"ER", @"297", @"AW", @"298", @"FO", @"299", @"GL",
+ @"350", @"GI", @"351", @"PT", @"352", @"LU", @"353", @"IE", @"354", @"IS", @"355", @"AL", @"356", @"MT", @"357", @"CY", @"358", @"FI",@"358", @"AX", @"359", @"BG",
+ @"370", @"LT", @"371", @"LV", @"372", @"EE", @"373", @"MD", @"374", @"AM", @"375", @"BY", @"376", @"AD", @"377", @"MC", @"378", @"SM", @"379", @"VA",
+ @"380", @"UA", @"381", @"RS", @"382", @"ME", @"385", @"HR", @"386", @"SI", @"387", @"BA", @"389", @"MK",
+ @"420", @"CZ", @"421", @"SK", @"423", @"LI",
+ @"500", @"FK", @"501", @"BZ", @"502", @"GT", @"503", @"SV", @"504", @"HN", @"505", @"NI", @"506", @"CR", @"507", @"PA", @"508", @"PM", @"509", @"HT",
+ @"590", @"GP", @"590", @"BL", @"590", @"MF", @"591", @"BO", @"592", @"GY", @"593", @"EC", @"594", @"GF", @"595", @"PY", @"596", @"MQ", @"597", @"SR", @"598", @"UY", @"599", @"CW", @"599", @"BQ",
+ @"670", @"TL", @"672", @"NF", @"673", @"BN", @"674", @"NR", @"675", @"PG", @"676", @"TO", @"677", @"SB", @"678", @"VU", @"679", @"FJ",
+ @"680", @"PW", @"681", @"WF", @"682", @"CK", @"683", @"NU", @"685", @"WS", @"686", @"KI", @"687", @"NC", @"688", @"TV", @"689", @"PF",
+ @"690", @"TK", @"691", @"FM", @"692", @"MH",
+ @"800", @"001", @"808", @"001",
+ @"850", @"KP", @"852", @"HK", @"853", @"MO", @"855", @"KH", @"856", @"LA",
+ @"870", @"001", @"878", @"001",
+ @"880", @"BD", @"881", @"001", @"882", @"001", @"883", @"001", @"886", @"TW", @"888", @"001",
+ @"960", @"MV", @"961", @"LB", @"962", @"JO", @"963", @"SY", @"964", @"IQ", @"965", @"KW", @"966", @"SA", @"967", @"YE", @"968", @"OM",
+ @"970", @"PS", @"971", @"AE", @"972", @"IL", @"973", @"BH", @"974", @"QA", @"975", @"BT", @"976", @"MN", @"977", @"NP", @"979", @"001",
+ @"992", @"TJ", @"993", @"TM", @"994", @"AZ", @"995", @"GE", @"996", @"KG", @"998", @"UZ",
+ nil];
+}
+
+/*
+ - (void)initCN2CC
+ {
+ [self clearCN2CC];
+ _mapCN2CCode = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @[@"US", @"AG", @"AI", @"AS", @"BB", @"BM", @"BS", @"CA", @"DM", @"DO", @"GD", @"GU", @"JM", @"KN", @"KY", @"LC", @"MP", @"MS", @"PR", @"SX", @"TC", @"TT", @"VC", @"VG", @"VI"], @"1", @[@"RU", @"KZ"], @"7",
+ @[@"EG"], @"20", @[@"ZA"], @"27",
+ @[@"GR"], @"30", @[@"NL"], @"31", @[@"BE"], @"32", @[@"FR"], @"33", @[@"ES"], @"34", @[@"HU"], @"36", @[@"IT"], @"39",
+ @[@"RO"], @"40", @[@"CH"], @"41", @[@"AT"], @"43", @[@"GB", @"GG", @"IM", @"JE"], @"44", @[@"DK"], @"45", @[@"SE"], @"46", @[@"NO", @"SJ"], @"47", @[@"PL"], @"48", @[@"DE"], @"49",
+ @[@"PE"], @"51", @[@"MX"], @"52", @[@"CU"], @"53", @[@"AR"], @"54", @[@"BR"], @"55", @[@"CL"], @"56", @[@"CO"], @"57", @[@"VE"], @"58",
+ @[@"MY"], @"60", @[@"AU", @"CC", @"CX"], @"61", @[@"ID"], @"62", @[@"PH"], @"63", @[@"NZ"], @"64", @[@"SG"], @"65", @[@"TH"], @"66",
+ @[@"JP"], @"81", @[@"KR"], @"82", @[@"VN"], @"84", @[@"CN"], @"86",
+ @[@"TR"], @"90", @[@"IN"], @"91", @[@"PK"], @"92", @[@"AF"], @"93", @[@"LK"], @"94", @[@"MM"], @"95", @[@"IR"], @"98",
+ @[@"SS"], @"211", @[@"MA", @"EH"], @"212", @[@"DZ"], @"213", @[@"TN"], @"216", @[@"LY"], @"218",
+ @[@"GM"], @"220", @[@"SN"], @"221", @[@"MR"], @"222", @[@"ML"], @"223", @[@"GN"], @"224", @[@"CI"], @"225", @[@"BF"], @"226", @[@"NE"], @"227", @[@"TG"], @"228", @[@"BJ"], @"229",
+ @[@"MU"], @"230", @[@"LR"], @"231", @[@"SL"], @"232", @[@"GH"], @"233", @[@"NG"], @"234", @[@"TD"], @"235", @[@"CF"], @"236", @[@"CM"], @"237", @[@"CV"], @"238", @[@"ST"], @"239",
+ @[@"GQ"], @"240", @[@"GA"], @"241", @[@"CG"], @"242", @[@"CD"], @"243", @[@"AO"], @"244", @[@"GW"], @"245", @[@"IO"], @"246", @[@"AC"], @"247", @[@"SC"], @"248", @[@"SD"], @"249",
+ @[@"RW"], @"250", @[@"ET"], @"251", @[@"SO"], @"252", @[@"DJ"], @"253", @[@"KE"], @"254", @[@"TZ"], @"255", @[@"UG"], @"256", @[@"BI"], @"257", @[@"MZ"], @"258",
+ @[@"ZM"], @"260", @[@"MG"], @"261", @[@"RE", @"YT"], @"262", @[@"ZW"], @"263", @[@"NA"], @"264", @[@"MW"], @"265", @[@"LS"], @"266", @[@"BW"], @"267", @[@"SZ"], @"268", @[@"KM"], @"269",
+ @[@"SH"], @"290", @[@"ER"], @"291", @[@"AW"], @"297", @[@"FO"], @"298", @[@"GL"], @"299",
+ @[@"GI"], @"350", @[@"PT"], @"351", @[@"LU"], @"352", @[@"IE"], @"353", @[@"IS"], @"354", @[@"AL"], @"355", @[@"MT"], @"356", @[@"CY"], @"357", @[@"FI", @"AX"], @"358", @[@"BG"], @"359",
+ @[@"LT"], @"370", @[@"LV"], @"371", @[@"EE"], @"372", @[@"MD"], @"373", @[@"AM"], @"374", @[@"BY"], @"375", @[@"AD"], @"376", @[@"MC"], @"377", @[@"SM"], @"378", @[@"VA"], @"379",
+ @[@"UA"], @"380", @[@"RS"], @"381", @[@"ME"], @"382", @[@"HR"], @"385", @[@"SI"], @"386", @[@"BA"], @"387", @[@"MK"], @"389",
+ @[@"CZ"], @"420", @[@"SK"], @"421", @[@"LI"], @"423",
+ @[@"FK"], @"500", @[@"BZ"], @"501", @[@"GT"], @"502", @[@"SV"], @"503", @[@"HN"], @"504", @[@"NI"], @"505", @[@"CR"], @"506", @[@"PA"], @"507", @[@"PM"], @"508", @[@"HT"], @"509",
+ @[@"GP", @"BL", @"MF"], @"590", @[@"BO"], @"591", @[@"GY"], @"592", @[@"EC"], @"593", @[@"GF"], @"594", @[@"PY"], @"595", @[@"MQ"], @"596", @[@"SR"], @"597", @[@"UY"], @"598", @[@"CW", @"BQ"], @"599",
+ @[@"TL"], @"670", @[@"NF"], @"672", @[@"BN"], @"673", @[@"NR"], @"674", @[@"PG"], @"675", @[@"TO"], @"676", @[@"SB"], @"677", @[@"VU"], @"678", @[@"FJ"], @"679",
+ @[@"PW"], @"680", @[@"WF"], @"681", @[@"CK"], @"682", @[@"NU"], @"683", @[@"WS"], @"685", @[@"KI"], @"686", @[@"NC"], @"687", @[@"TV"], @"688", @[@"PF"], @"689",
+ @[@"TK"], @"690", @[@"FM"], @"691", @[@"MH"], @"692",
+ @[@"001"], @"800", @[@"001"], @"808",
+ @[@"KP"], @"850", @[@"HK"], @"852", @[@"MO"], @"853", @[@"KH"], @"855", @[@"LA"], @"856",
+ @[@"001"], @"870", @[@"001"], @"878",
+ @[@"BD"], @"880", @[@"001"], @"881", @[@"001"], @"882", @[@"001"], @"883", @[@"TW"], @"886", @[@"001"], @"888",
+ @[@"MV"], @"960", @[@"LB"], @"961", @[@"JO"], @"962", @[@"SY"], @"963", @[@"IQ"], @"964", @[@"KW"], @"965", @[@"SA"], @"966", @[@"YE"], @"967", @[@"OM"], @"968",
+ @[@"PS"], @"970", @[@"AE"], @"971", @[@"IL"], @"972", @[@"BH"], @"973", @[@"QA"], @"974", @[@"BT"], @"975", @[@"MN"], @"976", @[@"NP"], @"977", @[@"001"], @"979",
+ @[@"TJ"], @"992", @[@"TM"], @"993", @[@"AZ"], @"994", @[@"GE"], @"995", @[@"KG"], @"996", @[@"UZ"], @"998", nil];
+ }
+ */
+
+
+#pragma mark - Metadata manager (phonenumberutil.js) functions -
+/**
+ * Attempts to extract a possible number from the string passed in. This
+ * currently strips all leading characters that cannot be used to start a phone
+ * number. Characters that can be used to start a phone number are defined in
+ * the VALID_START_CHAR_PATTERN. If none of these characters are found in the
+ * number passed in, an empty string is returned. This function also attempts to
+ * strip off any alternative extensions or endings if two or more are present,
+ * such as in the case of: (530) 583-6985 x302/x2303. The second extension here
+ * makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985
+ * x2303. We remove the second extension so that the first number is parsed
+ * correctly.
+ *
+ * @param {string} number the string that might contain a phone number.
+ * @return {string} the number, stripped of any non-phone-number prefix (such as
+ * 'Tel:') or an empty string if no character used to start phone numbers
+ * (such as + or any digit) is found in the number.
+ */
+- (NSString*)extractPossibleNumber:(NSString*)number
+{
+ NSString *possibleNumber = @"";
+ NSInteger start = [self stringPositionByRegex:number regex:self.VALID_START_CHAR_PATTERN_];
+
+ if (start >= 0)
+ {
+ possibleNumber = [number substringFromIndex:start];
+ // Remove trailing non-alpha non-numerical characters.
+ possibleNumber = [self replaceStringByRegex:possibleNumber regex:self.UNWANTED_END_CHAR_PATTERN_ withTemplate:@""];
+
+ // Check for extra numbers at the end.
+ NSInteger secondNumberStart = [self stringPositionByRegex:number regex:self.SECOND_NUMBER_START_PATTERN_];
+ if (secondNumberStart >= 0)
+ {
+ possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, secondNumberStart - 1)];
+ }
+ }
+ else
+ {
+ possibleNumber = @"";
+ }
+
+ return possibleNumber;
+
+ /*
+ NSString *possibleNumber = @"";
+ NSRegularExpression *currentPattern = self.VALID_START_CHAR_PATTERN_;
+ int sourceLength = phoneNumber.length;
+
+ NSArray *matches = [currentPattern matchesInString:phoneNumber options:0 range:NSMakeRange(0, sourceLength)];
+ if (matches && [matches count] > 0)
+ {
+ NSRange rangeOfFirstMatch = ((NSTextCheckingResult*)[matches objectAtIndex:0]).range;
+ possibleNumber = [phoneNumber substringFromIndex:rangeOfFirstMatch.location];
+
+ // Remove trailing non-alpha non-numerical characters.
+ currentPattern = self.UNWANTED_END_CHAR_PATTERN_;
+ possibleNumber = [currentPattern stringByReplacingMatchesInString:possibleNumber options:0
+ range:NSMakeRange(0, [possibleNumber length]) withTemplate:@""];
+ // Check for extra numbers at the end.
+ currentPattern = self.SECOND_NUMBER_START_PATTERN_;
+ matches = [currentPattern matchesInString:possibleNumber options:0
+ range:NSMakeRange(0, [possibleNumber length])];
+ if (matches && [matches count] > 0)
+ {
+ NSRange rangeOfSecondMatch = ((NSTextCheckingResult*)[matches objectAtIndex:0]).range;
+ possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, rangeOfSecondMatch.location)];
+ }
+ }
+
+ return possibleNumber;
+ */
+}
+
+
+/**
+ * Checks to see if the string of characters could possibly be a phone number at
+ * all. At the moment, checks to see that the string begins with at least 2
+ * digits, ignoring any punctuation commonly found in phone numbers. This method
+ * does not require the number to be normalized in advance - but does assume
+ * that leading non-number symbols have been removed, such as by the method
+ * extractPossibleNumber.
+ *
+ * @param {string} number string to be checked for viability as a phone number.
+ * @return {boolean} NO if the number could be a phone number of some sort,
+ * otherwise NO.
+ */
+- (BOOL)isViablePhoneNumber:(NSString*)phoneNumber
+{
+ if (phoneNumber.length < MIN_LENGTH_FOR_NSN_)
+ {
+ return NO;
+ }
+
+ return [self matchesEntirely:self.VALID_PHONE_NUMBER_PATTERN_ string:phoneNumber];
+}
+
+
+/**
+ * Normalizes a string of characters representing a phone number. This performs
+ * the following conversions:
+ * Punctuation is stripped.
+ * For ALPHA/VANITY numbers:
+ * Letters are converted to their numeric representation on a telephone
+ * keypad. The keypad used here is the one defined in ITU Recommendation
+ * E.161. This is only done if there are 3 or more letters in the number,
+ * to lessen the risk that such letters are typos.
+ * For other numbers:
+ * Wide-ascii digits are converted to normal ASCII (European) digits.
+ * Arabic-Indic numerals are converted to European numerals.
+ * Spurious alpha characters are stripped.
+ *
+ * @param {string} number a string of characters representing a phone number.
+ * @return {string} the normalized string version of the phone number.
+ */
+- (NSString*)normalizePhoneNumber:(NSString*)number
+{
+ if ([self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:number])
+ {
+ return [self normalizeHelper:number normalizationReplacements:self.ALL_NORMALIZATION_MAPPINGS_ removeNonMatches:true];
+ }
+ else
+ {
+ return [self normalizeDigitsOnly:number];
+ }
+
+ return nil;
+}
+
+
+/**
+ * Normalizes a string of characters representing a phone number. This is a
+ * wrapper for normalize(String number) but does in-place normalization of the
+ * StringBuffer provided.
+ *
+ * @param {!goog.string.StringBuffer} number a StringBuffer of characters
+ * representing a phone number that will be normalized in place.
+ * @private
+ */
+
+- (void)normalizeSB:(NSString**)number
+{
+ if (number == NULL)
+ {
+ return;
+ }
+
+ (*number) = [self normalizePhoneNumber:(*number)];
+}
+
+
+/**
+ * Normalizes a string of characters representing a phone number. This converts
+ * wide-ascii and arabic-indic numerals to European numerals, and strips
+ * punctuation and alpha characters.
+ *
+ * @param {string} number a string of characters representing a phone number.
+ * @return {string} the normalized string version of the phone number.
+ */
+- (NSString*)normalizeDigitsOnly:(NSString*)number
+{
+ return [self stringByReplacingOccurrencesString:number
+ withMap:self.DIGIT_MAPPINGS removeNonMatches:YES];
+}
+
+
+/**
+ * Converts all alpha characters in a number to their respective digits on a
+ * keypad, but retains existing formatting. Also converts wide-ascii digits to
+ * normal ascii digits, and converts Arabic-Indic numerals to European numerals.
+ *
+ * @param {string} number a string of characters representing a phone number.
+ * @return {string} the normalized string version of the phone number.
+ */
+- (NSString*)convertAlphaCharactersInNumber:(NSString*)number
+{
+ return [self stringByReplacingOccurrencesString:number
+ withMap:self.ALL_NORMALIZATION_MAPPINGS_ removeNonMatches:NO];
+}
+
+
+/**
+ * Gets the length of the geographical area code from the
+ * {@code national_number} field of the PhoneNumber object passed in, so that
+ * clients could use it to split a national significant number into geographical
+ * area code and subscriber number. It works in such a way that the resultant
+ * subscriber number should be diallable, at least on some devices. An example
+ * of how this could be used:
+ *
+ * <pre>
+ * var phoneUtil = getInstance();
+ * var number = phoneUtil.parse('16502530000', 'US');
+ * var nationalSignificantNumber =
+ * phoneUtil.getNationalSignificantNumber(number);
+ * var areaCode;
+ * var subscriberNumber;
+ *
+ * var areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
+ * if (areaCodeLength > 0) {
+ * areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
+ * subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
+ * } else {
+ * areaCode = '';
+ * subscriberNumber = nationalSignificantNumber;
+ * }
+ * </pre>
+ *
+ * N.B.: area code is a very ambiguous concept, so the I18N team generally
+ * recommends against using it for most purposes, but recommends using the more
+ * general {@code national_number} instead. Read the following carefully before
+ * deciding to use this method:
+ * <ul>
+ * <li> geographical area codes change over time, and this method honors those
+ * changes; therefore, it doesn't guarantee the stability of the result it
+ * produces.
+ * <li> subscriber numbers may not be diallable from all devices (notably
+ * mobile devices, which typically requires the full national_number to be
+ * dialled in most regions).
+ * <li> most non-geographical numbers have no area codes, including numbers
+ * from non-geographical entities.
+ * <li> some geographical numbers have no area codes.
+ * </ul>
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
+ * which clients want to know the length of the area code.
+ * @return {number} the length of area code of the PhoneNumber object passed in.
+ */
+- (NSUInteger)getLengthOfGeographicalAreaCode:(NBPhoneNumber*)phoneNumber error:(NSError **)error
+{
+ NSUInteger res = 0;
+ @try {
+ res = [self getLengthOfGeographicalAreaCode:phoneNumber];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ {
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ }
+ return res;
+}
+
+
+- (NSUInteger)getLengthOfGeographicalAreaCode:(NBPhoneNumber*)phoneNumber
+{
+ NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode];
+
+ if (metadata == nil)
+ {
+ return 0;
+ }
+ // If a country doesn't use a national prefix, and this number doesn't have
+ // an Italian leading zero, we assume it is a closed dialling plan with no
+ // area codes.
+ if (metadata.nationalPrefix == nil && phoneNumber.italianLeadingZero == NO)
+ {
+ return 0;
+ }
+
+ if ([self isNumberGeographical:phoneNumber] == NO)
+ {
+ return 0;
+ }
+
+ return [self getLengthOfNationalDestinationCode:phoneNumber];
+}
+
+
+/**
+ * Gets the length of the national destination code (NDC) from the PhoneNumber
+ * object passed in, so that clients could use it to split a national
+ * significant number into NDC and subscriber number. The NDC of a phone number
+ * is normally the first group of digit(s) right after the country calling code
+ * when the number is formatted in the international format, if there is a
+ * subscriber number part that follows. An example of how this could be used:
+ *
+ * <pre>
+ * var phoneUtil = getInstance();
+ * var number = phoneUtil.parse('18002530000', 'US');
+ * var nationalSignificantNumber =
+ * phoneUtil.getNationalSignificantNumber(number);
+ * var nationalDestinationCode;
+ * var subscriberNumber;
+ *
+ * var nationalDestinationCodeLength =
+ * phoneUtil.getLengthOfNationalDestinationCode(number);
+ * if (nationalDestinationCodeLength > 0) {
+ * nationalDestinationCode =
+ * nationalSignificantNumber.substring(0, nationalDestinationCodeLength);
+ * subscriberNumber =
+ * nationalSignificantNumber.substring(nationalDestinationCodeLength);
+ * } else {
+ * nationalDestinationCode = '';
+ * subscriberNumber = nationalSignificantNumber;
+ * }
+ * </pre>
+ *
+ * Refer to the unittests to see the difference between this function and
+ * {@link #getLengthOfGeographicalAreaCode}.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
+ * which clients want to know the length of the NDC.
+ * @return {number} the length of NDC of the PhoneNumber object passed in.
+ */
+- (NSUInteger)getLengthOfNationalDestinationCode:(NBPhoneNumber*)phoneNumber error:(NSError **)error
+{
+ NSUInteger res = 0;
+
+ @try {
+ res = [self getLengthOfNationalDestinationCode:phoneNumber];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ {
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ }
+
+ return res;
+}
+
+
+- (NSUInteger)getLengthOfNationalDestinationCode:(NBPhoneNumber*)phoneNumber
+{
+ NBPhoneNumber *copiedProto = nil;
+ if ([self hasValue:phoneNumber.extension])
+ {
+ copiedProto = [phoneNumber copy];
+ copiedProto.extension = nil;
+ }
+ else
+ {
+ copiedProto = phoneNumber;
+ }
+
+ NSString *nationalSignificantNumber = [self format:copiedProto numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+ NSMutableArray *numberGroups = [[self componentsSeparatedByRegex:nationalSignificantNumber regex:NON_DIGITS_PATTERN_] mutableCopy];
+
+ // The pattern will start with '+COUNTRY_CODE ' so the first group will always
+ // be the empty string (before the + symbol) and the second group will be the
+ // country calling code. The third group will be area code if it is not the
+ // last group.
+ // NOTE: On IE the first group that is supposed to be the empty string does
+ // not appear in the array of number groups... so make the result on non-IE
+ // browsers to be that of IE.
+ if ([numberGroups count] > 0 && ((NSString*)[numberGroups objectAtIndex:0]).length <= 0)
+ {
+ [numberGroups removeObjectAtIndex:0];
+ }
+
+ if ([numberGroups count] <= 2)
+ {
+ return 0;
+ }
+
+ NSArray *regionCodes = [self regionCodeFromCountryCode:phoneNumber.countryCode];
+ BOOL isExists = NO;
+
+ for (NSString *regCode in regionCodes)
+ {
+ if ([regCode isEqualToString:@"AR"])
+ {
+ isExists = YES;
+ break;
+ }
+ }
+
+ if (isExists && [self getNumberType:phoneNumber] == NBEPhoneNumberTypeMOBILE)
+ {
+ // Argentinian mobile numbers, when formatted in the international format,
+ // are in the form of +54 9 NDC XXXX.... As a result, we take the length of
+ // the third group (NDC) and add 1 for the digit 9, which also forms part of
+ // the national significant number.
+ //
+ // TODO: Investigate the possibility of better modeling the metadata to make
+ // it easier to obtain the NDC.
+ return ((NSString*)[numberGroups objectAtIndex:2]).length + 1;
+ }
+
+ return ((NSString*)[numberGroups objectAtIndex:1]).length;
+}
+
+
+/**
+ * Normalizes a string of characters representing a phone number by replacing
+ * all characters found in the accompanying map with the values therein, and
+ * stripping all other characters if removeNonMatches is NO.
+ *
+ * @param {string} number a string of characters representing a phone number.
+ * @param {!Object.<string, string>} normalizationReplacements a mapping of
+ * characters to what they should be replaced by in the normalized version
+ * of the phone number.
+ * @param {boolean} removeNonMatches indicates whether characters that are not
+ * able to be replaced should be stripped from the number. If this is NO,
+ * they will be left unchanged in the number.
+ * @return {string} the normalized string version of the phone number.
+ * @private
+ */
+- (NSString*)normalizeHelper:(NSString*)sourceString normalizationReplacements:(NSDictionary*)normalizationReplacements
+ removeNonMatches:(BOOL)removeNonMatches
+{
+ NSMutableString *normalizedNumber = [[NSMutableString alloc] init];
+ unichar character = 0;
+ NSString *newDigit = @"";
+ NSUInteger numberLength = sourceString.length;
+
+ for (NSUInteger i = 0; i<numberLength; ++i)
+ {
+ character = [sourceString characterAtIndex:i];
+ newDigit = [normalizationReplacements objectForKey:[[NSString stringWithFormat: @"%C", character] uppercaseString]];
+ if (newDigit != nil)
+ {
+ [normalizedNumber appendString:newDigit];
+ }
+ else if (removeNonMatches == NO)
+ {
+ [normalizedNumber appendString:[NSString stringWithFormat: @"%C", character]];
+ }
+ // If neither of the above are NO, we remove this character.
+
+ //NSLog(@"[%@]", normalizedNumber);
+ }
+
+ return normalizedNumber;
+}
+
+
+/**
+ * Helper function to check if the national prefix formatting rule has the first
+ * group only, i.e., does not start with the national prefix.
+ *
+ * @param {string} nationalPrefixFormattingRule The formatting rule for the
+ * national prefix.
+ * @return {boolean} NO if the national prefix formatting rule has the first
+ * group only.
+ */
+- (BOOL)formattingRuleHasFirstGroupOnly:(NSString*)nationalPrefixFormattingRule
+{
+ BOOL hasFound = NO;
+ if ([self stringPositionByRegex:nationalPrefixFormattingRule regex:FIRST_GROUP_ONLY_PREFIX_PATTERN_] >= 0)
+ {
+ hasFound = YES;
+ }
+
+ return (([nationalPrefixFormattingRule length] == 0) || hasFound);
+}
+
+
+/**
+ * Tests whether a phone number has a geographical association. It checks if
+ * the number is associated to a certain region in the country where it belongs
+ * to. Note that this doesn't verify if the number is actually in use.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} phoneNumber The phone number to test.
+ * @return {boolean} NO if the phone number has a geographical association.
+ * @private
+ */
+- (BOOL)isNumberGeographical:(NBPhoneNumber*)phoneNumber
+{
+ NBEPhoneNumberType numberType = [self getNumberType:phoneNumber];
+ // TODO: Include mobile phone numbers from countries like Indonesia, which
+ // has some mobile numbers that are geographical.
+ return numberType == NBEPhoneNumberTypeFIXED_LINE || numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
+}
+
+
+/**
+ * Helper function to check region code is not unknown or nil.
+ *
+ * @param {?string} regionCode the ISO 3166-1 two-letter region code.
+ * @return {boolean} NO if region code is valid.
+ * @private
+ */
+- (BOOL)isValidRegionCode:(NSString*)regionCode
+{
+ // In Java we check whether the regionCode is contained in supportedRegions
+ // that is built out of all the values of countryCallingCodeToRegionCodeMap
+ // (countryCodeToRegionCodeMap in JS) minus REGION_CODE_FOR_NON_GEO_ENTITY.
+ // In JS we check whether the regionCode is contained in the keys of
+ // countryToMetadata but since for non-geographical country calling codes
+ // (e.g. +800) we use the country calling codes instead of the region code as
+ // key in the map we have to make sure regionCode is not a number to prevent
+ // returning NO for non-geographical country calling codes.
+ return [self hasValue:regionCode] && [self isNaN:regionCode] && [self getMetadataForRegion:regionCode.uppercaseString] != nil;
+}
+
+
+/**
+ * Helper function to check the country calling code is valid.
+ *
+ * @param {number} countryCallingCode the country calling code.
+ * @return {boolean} NO if country calling code code is valid.
+ * @private
+ */
+- (BOOL)hasValidCountryCallingCode:(NSNumber *)countryCallingCode
+{
+ id res = [self regionCodeFromCountryCode:countryCallingCode];
+ if (res != nil)
+ {
+ return YES;
+ }
+
+ return NO;
+}
+
+
+/**
+ * Formats a phone number in the specified format using default rules. Note that
+ * this does not promise to produce a phone number that the user can dial from
+ * where they are - although we do format in either 'national' or
+ * 'international' format depending on what the client asks for, we do not
+ * currently support a more abbreviated format, such as for users in the same
+ * 'area' who could potentially dial the number without area code. Note that if
+ * the phone number has a country calling code of 0 or an otherwise invalid
+ * country calling code, we cannot work out which formatting rules to apply so
+ * we return the national significant number with no formatting applied.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @return {string} the formatted phone number.
+ */
+- (NSString*)format:(NBPhoneNumber*)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat error:(NSError**)error;
+{
+ NSString *res = nil;
+ @try {
+ res = [self format:phoneNumber numberFormat:numberFormat];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NSString*)format:(NBPhoneNumber*)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat
+{
+ if ([phoneNumber.nationalNumber isEqualToNumber:@0] && [self hasValue:phoneNumber.rawInput])
+ {
+ // Unparseable numbers that kept their raw input just use that.
+ // This is the only case where a number can be formatted as E164 without a
+ // leading '+' symbol (but the original number wasn't parseable anyway).
+ // TODO: Consider removing the 'if' above so that unparseable strings
+ // without raw input format to the empty string instead of "+00"
+ /** @type {string} */
+ NSString *rawInput = phoneNumber.rawInput;
+ if ([self hasValue:rawInput]) {
+ return rawInput;
+ }
+ }
+
+ NSNumber *countryCallingCode = phoneNumber.countryCode;
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];
+
+ if (numberFormat == NBEPhoneNumberFormatE164)
+ {
+ // Early exit for E164 case (even if the country calling code is invalid)
+ // since no formatting of the national number needs to be applied.
+ // Extensions are not formatted.
+ return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatE164
+ formattedNationalNumber:nationalSignificantNumber formattedExtension:@""];
+ }
+
+ if ([self hasValidCountryCallingCode:countryCallingCode] == NO)
+ {
+ return nationalSignificantNumber;
+ }
+
+ // Note getRegionCodeForCountryCode() is used because formatting information
+ // for regions which share a country calling code is contained by only one
+ // region for performance reasons. For example, for NANPA regions it will be
+ // contained in the metadata for US.
+ NSArray *regionCodeArray = [self regionCodeFromCountryCode:countryCallingCode];
+ NSString *regionCode = [regionCodeArray objectAtIndex:0];
+
+ // Metadata cannot be nil because the country calling code is valid (which
+ // means that the region code cannot be ZZ and must be one of our supported
+ // region codes).
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
+ NSString *formattedExtension = [self maybeGetFormattedExtension:phoneNumber metadata:metadata numberFormat:numberFormat];
+ NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadata phoneNumberFormat:numberFormat carrierCode:nil];
+
+ return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:numberFormat
+ formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension];
+}
+
+
+/**
+ * Formats a phone number in the specified format using client-defined
+ * formatting rules. Note that if the phone number has a country calling code of
+ * zero or an otherwise invalid country calling code, we cannot work out things
+ * like whether there should be a national prefix applied, or how to format
+ * extensions, so we return the national significant number with no formatting
+ * applied.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting
+ * rules specified by clients.
+ * @return {string} the formatted phone number.
+ */
+- (NSString*)formatByPattern:(NBPhoneNumber*)number numberFormat:(NBEPhoneNumberFormat)numberFormat userDefinedFormats:(NSArray*)userDefinedFormats error:(NSError**)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatByPattern:number numberFormat:numberFormat userDefinedFormats:userDefinedFormats];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NSString*)formatByPattern:(NBPhoneNumber*)number numberFormat:(NBEPhoneNumberFormat)numberFormat userDefinedFormats:(NSArray*)userDefinedFormats
+{
+ NSNumber *countryCallingCode = number.countryCode;
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
+
+ if ([self hasValidCountryCallingCode:countryCallingCode] == NO)
+ {
+ return nationalSignificantNumber;
+ }
+
+ // Note getRegionCodeForCountryCode() is used because formatting information
+ // for regions which share a country calling code is contained by only one
+ // region for performance reasons. For example, for NANPA regions it will be
+ // contained in the metadata for US.
+ NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode];
+ NSString *regionCode = nil;
+ if (regionCodes != nil && regionCodes.count > 0)
+ {
+ regionCode = [regionCodes objectAtIndex:0];
+ }
+
+ // Metadata cannot be nil because the country calling code is valid
+ /** @type {i18n.phonenumbers.PhoneMetadata} */
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
+
+ NSString *formattedNumber = @"";
+ NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:userDefinedFormats nationalNumber:nationalSignificantNumber];
+ if (formattingPattern == nil)
+ {
+ // If no pattern above is matched, we format the number as a whole.
+ formattedNumber = nationalSignificantNumber;
+ }
+ else
+ {
+ // Before we do a replacement of the national prefix pattern $NP with the
+ // national prefix, we need to copy the rule so that subsequent replacements
+ // for different numbers have the appropriate national prefix.
+ NBNumberFormat *numFormatCopy = [formattingPattern copy];
+ NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;
+
+ if (nationalPrefixFormattingRule.length > 0)
+ {
+ NSString *nationalPrefix = metadata.nationalPrefix;
+ if (nationalPrefix.length > 0)
+ {
+ // Replace $NP with national prefix and $FG with the first group ($1).
+ nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule regex:NP_PATTERN_ withTemplate:nationalPrefix];
+ nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule regex:FG_PATTERN_ withTemplate:@"\\$1"];
+ numFormatCopy.nationalPrefixFormattingRule = nationalPrefixFormattingRule;
+ }
+ else
+ {
+ // We don't want to have a rule for how to format the national prefix if
+ // there isn't one.
+ numFormatCopy.nationalPrefixFormattingRule = @"";
+ }
+ }
+
+ formattedNumber = [self formatNsnUsingPattern:nationalSignificantNumber
+ formattingPattern:numFormatCopy numberFormat:numberFormat carrierCode:nil];
+ }
+
+ NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadata numberFormat:numberFormat];
+
+ //NSLog(@"!@# prefixNumberWithCountryCallingCode called [%@]", formattedExtension);
+ return [self prefixNumberWithCountryCallingCode:countryCallingCode
+ phoneNumberFormat:numberFormat
+ formattedNationalNumber:formattedNumber
+ formattedExtension:formattedExtension];
+}
+
+
+/**
+ * Formats a phone number in national format for dialing using the carrier as
+ * specified in the {@code carrierCode}. The {@code carrierCode} will always be
+ * used regardless of whether the phone number already has a preferred domestic
+ * carrier code stored. If {@code carrierCode} contains an empty string, returns
+ * the number in national format without any carrier code.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {string} carrierCode the carrier selection code to be used.
+ * @return {string} the formatted phone number in national format for dialing
+ * using the carrier as specified in the {@code carrierCode}.
+ */
+- (NSString*)formatNationalNumberWithCarrierCode:(NBPhoneNumber*)number carrierCode:(NSString*)carrierCode error:(NSError **)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatNationalNumberWithCarrierCode:number carrierCode:carrierCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NSString*)formatNationalNumberWithCarrierCode:(NBPhoneNumber*)number carrierCode:(NSString*)carrierCode
+{
+ NSNumber *countryCallingCode = number.countryCode;
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
+ if ([self hasValidCountryCallingCode:countryCallingCode] == NO)
+ {
+ return nationalSignificantNumber;
+ }
+
+ // Note getRegionCodeForCountryCode() is used because formatting information
+ // for regions which share a country calling code is contained by only one
+ // region for performance reasons. For example, for NANPA regions it will be
+ // contained in the metadata for US.
+ NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
+ // Metadata cannot be nil because the country calling code is valid.
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
+ NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadata numberFormat:NBEPhoneNumberFormatNATIONAL];
+ NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadata phoneNumberFormat:NBEPhoneNumberFormatNATIONAL carrierCode:carrierCode];
+ return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatNATIONAL formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension];
+}
+
+
+/**
+ * @param {number} countryCallingCode
+ * @param {?string} regionCode
+ * @return {i18n.phonenumbers.PhoneMetadata}
+ * @private
+ */
+- (NBPhoneMetaData*)getMetadataForRegionOrCallingCode:(NSNumber*)countryCallingCode regionCode:(NSString*)regionCode
+{
+ return [_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] ?
+ [self getMetadataForNonGeographicalRegion:countryCallingCode] : [self getMetadataForRegion:regionCode];
+}
+
+
+/**
+ * Formats a phone number in national format for dialing using the carrier as
+ * specified in the preferred_domestic_carrier_code field of the PhoneNumber
+ * object passed in. If that is missing, use the {@code fallbackCarrierCode}
+ * passed in instead. If there is no {@code preferred_domestic_carrier_code},
+ * and the {@code fallbackCarrierCode} contains an empty string, return the
+ * number in national format without any carrier code.
+ *
+ * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier
+ * code passed in should take precedence over the number's
+ * {@code preferred_domestic_carrier_code} when formatting.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {string} fallbackCarrierCode the carrier selection code to be used, if
+ * none is found in the phone number itself.
+ * @return {string} the formatted phone number in national format for dialing
+ * using the number's preferred_domestic_carrier_code, or the
+ * {@code fallbackCarrierCode} passed in if none is found.
+ */
+- (NSString*)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber*)number
+ fallbackCarrierCode:(NSString*)fallbackCarrierCode error:(NSError **)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatNationalNumberWithCarrierCode:number carrierCode:fallbackCarrierCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return res;
+}
+
+
+- (NSString*)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber*)number fallbackCarrierCode:(NSString*)fallbackCarrierCode
+{
+ NSString *domesticCarrierCode = number.preferredDomesticCarrierCode != nil ? number.preferredDomesticCarrierCode : fallbackCarrierCode;
+ return [self formatNationalNumberWithCarrierCode:number carrierCode:domesticCarrierCode];
+}
+
+
+/**
+ * Returns a number formatted in such a way that it can be dialed from a mobile
+ * phone in a specific region. If the number cannot be reached from the region
+ * (e.g. some countries block toll-free numbers from being called outside of the
+ * country), the method returns an empty string.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {string} regionCallingFrom the region where the call is being placed.
+ * @param {boolean} withFormatting whether the number should be returned with
+ * formatting symbols, such as spaces and dashes.
+ * @return {string} the formatted phone number.
+ */
+- (NSString*)formatNumberForMobileDialing:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom withFormatting:(BOOL)withFormatting error:(NSError**)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatNumberForMobileDialing:number regionCallingFrom:regionCallingFrom withFormatting:withFormatting];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NSString*)formatNumberForMobileDialing:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom withFormatting:(BOOL)withFormatting
+{
+ NSNumber *countryCallingCode = number.countryCode;
+ if ([self hasValidCountryCallingCode:countryCallingCode] == NO)
+ {
+ return [self hasValue:number.rawInput] ? number.rawInput : @"";
+ }
+
+ NSString *formattedNumber = @"";
+ // Clear the extension, as that part cannot normally be dialed together with
+ // the main number.
+ NBPhoneNumber *numberNoExt = [number copy];
+ numberNoExt.extension = @"";
+
+ NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
+ if ([regionCallingFrom isEqualToString:regionCode])
+ {
+ NBEPhoneNumberType numberType = [self getNumberType:numberNoExt];
+ BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE) || (numberType == NBEPhoneNumberTypeMOBILE) || (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
+ // Carrier codes may be needed in some countries. We handle this here.
+ if ([regionCode isEqualToString:@"CO"] && numberType == NBEPhoneNumberTypeFIXED_LINE)
+ {
+ formattedNumber = [self formatNationalNumberWithCarrierCode:numberNoExt
+ carrierCode:COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX_];
+ }
+ else if ([regionCode isEqualToString:@"BR"] && isFixedLineOrMobile)
+ {
+ formattedNumber = [self hasValue:numberNoExt.preferredDomesticCarrierCode] ?
+ [self formatNationalNumberWithPreferredCarrierCode:numberNoExt fallbackCarrierCode:@""] : @"";
+ // Brazilian fixed line and mobile numbers need to be dialed with a
+ // carrier code when called within Brazil. Without that, most of the
+ // carriers won't connect the call. Because of that, we return an
+ // empty string here.
+ }
+ else
+ {
+ // For NANPA countries, non-geographical countries, and Mexican fixed
+ // line and mobile numbers, we output international format for numbersi
+ // that can be dialed internationally as that always works.
+ if (([countryCallingCode isEqualToNumber:@(NANPA_COUNTRY_CODE_)] ||
+ [regionCode isEqualToString:_REGION_CODE_FOR_NON_GEO_ENTITY] ||
+ // MX fixed line and mobile numbers should always be formatted in
+ // international format, even when dialed within MX. For national
+ // format to work, a carrier code needs to be used, and the correct
+ // carrier code depends on if the caller and callee are from the
+ // same local area. It is trickier to get that to work correctly than
+ // using international format, which is tested to work fine on all
+ // carriers.
+ ([regionCode isEqualToString:@"MX"] && isFixedLineOrMobile)) && [self canBeInternationallyDialled:numberNoExt])
+ {
+ formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+ }
+ else
+ {
+ formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatNATIONAL];
+ }
+ }
+ }
+ else if ([self canBeInternationallyDialled:numberNoExt])
+ {
+ return withFormatting ? [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL] :
+ [self format:numberNoExt numberFormat:NBEPhoneNumberFormatE164];
+ }
+
+ return withFormatting ?
+ formattedNumber : [self normalizeHelper:formattedNumber normalizationReplacements:self.DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES];
+}
+
+
+/**
+ * Formats a phone number for out-of-country dialing purposes. If no
+ * regionCallingFrom is supplied, we format the number in its INTERNATIONAL
+ * format. If the country calling code is the same as that of the region where
+ * the number is from, then NATIONAL formatting will be applied.
+ *
+ * <p>If the number itself has a country calling code of zero or an otherwise
+ * invalid country calling code, then we return the number with no formatting
+ * applied.
+ *
+ * <p>Note this function takes care of the case for calling inside of NANPA and
+ * between Russia and Kazakhstan (who share the same country calling code). In
+ * those cases, no international prefix is used. For regions which have multiple
+ * international prefixes, the number in its INTERNATIONAL format will be
+ * returned instead.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
+ * formatted.
+ * @param {string} regionCallingFrom the region where the call is being placed.
+ * @return {string} the formatted phone number.
+ */
+- (NSString*)formatOutOfCountryCallingNumber:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError**)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return res;
+}
+
+- (NSString*)formatOutOfCountryCallingNumber:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom
+{
+ if ([self isValidRegionCode:regionCallingFrom] == NO)
+ {
+ return [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+ }
+
+ NSNumber *countryCallingCode = number.countryCode;
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
+ if ([self hasValidCountryCallingCode:countryCallingCode] == NO)
+ {
+ return nationalSignificantNumber;
+ }
+
+ if ([countryCallingCode isEqualToNumber: @(NANPA_COUNTRY_CODE_)])
+ {
+ if ([self isNANPACountry:regionCallingFrom])
+ {
+ // For NANPA regions, return the national format for these regions but
+ // prefix it with the country calling code.
+ return [NSString stringWithFormat:@"%@ %@", countryCallingCode, [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]];
+ }
+ }
+ else if ([countryCallingCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCallingFrom]])
+ {
+ // If regions share a country calling code, the country calling code need
+ // not be dialled. This also applies when dialling within a region, so this
+ // if clause covers both these cases. Technically this is the case for
+ // dialling from La Reunion to other overseas departments of France (French
+ // Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover
+ // this edge case for now and for those cases return the version including
+ // country calling code. Details here:
+ // http://www.petitfute.com/voyage/225-info-pratiques-reunion
+ return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
+ }
+ // Metadata cannot be nil because we checked 'isValidRegionCode()' above.
+ NBPhoneMetaData *metadataForRegionCallingFrom = [self getMetadataForRegion:regionCallingFrom];
+ NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;
+
+ // For regions that have multiple international prefixes, the international
+ // format of the number is returned, unless there is a preferred international
+ // prefix.
+ NSString *internationalPrefixForFormatting = @"";
+ if ([self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX_ string:internationalPrefix])
+ {
+ internationalPrefixForFormatting = internationalPrefix;
+ }
+ else if ([self hasValue:metadataForRegionCallingFrom.preferredInternationalPrefix])
+ {
+ internationalPrefixForFormatting = metadataForRegionCallingFrom.preferredInternationalPrefix;
+ }
+
+ NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
+ // Metadata cannot be nil because the country calling code is valid.
+ NBPhoneMetaData *metadataForRegion = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
+ NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadataForRegion
+ phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL carrierCode:nil];
+ NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadataForRegion numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+
+ NSString *hasLenth = [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCallingCode, formattedNationalNumber, formattedExtension];
+ NSString *hasNotLength = [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
+ formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension];
+
+ return internationalPrefixForFormatting.length > 0 ? hasLenth:hasNotLength;
+}
+
+
+/**
+ * A helper function that is used by format and formatByPattern.
+ *
+ * @param {number} countryCallingCode the country calling code.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @param {string} formattedNationalNumber
+ * @param {string} formattedExtension
+ * @return {string} the formatted phone number.
+ * @private
+ */
+- (NSString*)prefixNumberWithCountryCallingCode:(NSNumber*)countryCallingCode phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat
+ formattedNationalNumber:(NSString*)formattedNationalNumber
+ formattedExtension:(NSString*)formattedExtension
+{
+ switch (numberFormat)
+ {
+ case NBEPhoneNumberFormatE164:
+ return [NSString stringWithFormat:@"+%@%@%@", countryCallingCode, formattedNationalNumber, formattedExtension];
+ case NBEPhoneNumberFormatINTERNATIONAL:
+ return [NSString stringWithFormat:@"+%@ %@%@", countryCallingCode, formattedNationalNumber, formattedExtension];
+ case NBEPhoneNumberFormatRFC3966:
+ return [NSString stringWithFormat:@"%@+%@-%@%@", RFC3966_PREFIX_, countryCallingCode, formattedNationalNumber, formattedExtension];
+ case NBEPhoneNumberFormatNATIONAL:
+ default:
+ return [NSString stringWithFormat:@"%@%@", formattedNationalNumber, formattedExtension];
+ }
+}
+
+
+/**
+ * Formats a phone number using the original phone number format that the number
+ * is parsed from. The original format is embedded in the country_code_source
+ * field of the PhoneNumber object passed in. If such information is missing,
+ * the number will be formatted into the NATIONAL format by default. When the
+ * number contains a leading zero and this is unexpected for this country, or we
+ * don't have a formatting pattern for the number, the method returns the raw
+ * input when it is available.
+ *
+ * Note this method guarantees no digit will be inserted, removed or modified as
+ * a result of formatting.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
+ * be formatted in its original number format.
+ * @param {string} regionCallingFrom the region whose IDD needs to be prefixed
+ * if the original number has one.
+ * @return {string} the formatted phone number in its original number format.
+ */
+- (NSString*)formatInOriginalFormat:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError **)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatInOriginalFormat:number regionCallingFrom:regionCallingFrom];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return res;
+}
+
+
+- (NSString*)formatInOriginalFormat:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom
+{
+ if ([self hasValue:number.rawInput] && ([self hasUnexpectedItalianLeadingZero:number] || [self hasFormattingPatternForNumber:number] == NO))
+ {
+ // We check if we have the formatting pattern because without that, we might
+ // format the number as a group without national prefix.
+ return number.rawInput;
+ }
+
+ if (number.countryCodeSource == nil)
+ {
+ return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
+ }
+
+ NSString *formattedNumber = @"";
+
+ switch ([number.countryCodeSource intValue])
+ {
+ case NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN:
+ formattedNumber = [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+ break;
+ case NBECountryCodeSourceFROM_NUMBER_WITH_IDD:
+ formattedNumber = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
+ break;
+ case NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN:
+ formattedNumber = [[self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL] substringFromIndex:1];
+ break;
+ case NBECountryCodeSourceFROM_DEFAULT_COUNTRY:
+ // Fall-through to default case.
+ default:
+ {
+ NSString *regionCode = [self getRegionCodeForCountryCode:number.countryCode];
+ // We strip non-digits from the NDD here, and from the raw input later,
+ // so that we can compare them easily.
+ NSString *nationalPrefix = [self getNddPrefixForRegion:regionCode stripNonDigits:YES];
+ NSString *nationalFormat = [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
+ if (nationalPrefix == nil || nationalPrefix.length == 0)
+ {
+ // If the region doesn't have a national prefix at all, we can safely
+ // return the national format without worrying about a national prefix
+ // being added.
+ formattedNumber = nationalFormat;
+ break;
+ }
+ // Otherwise, we check if the original number was entered with a national
+ // prefix.
+ if ([self rawInputContainsNationalPrefix:number.rawInput nationalPrefix:nationalPrefix regionCode:regionCode])
+ {
+ // If so, we can safely return the national format.
+ formattedNumber = nationalFormat;
+ break;
+ }
+ // Metadata cannot be nil here because getNddPrefixForRegion() (above)
+ // returns nil if there is no metadata for the region.
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode];
+ NSString *nationalNumber = [self getNationalSignificantNumber:number];
+ NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber];
+ // The format rule could still be nil here if the national number was 0
+ // and there was no raw input (this should not be possible for numbers
+ // generated by the phonenumber library as they would also not have a
+ // country calling code and we would have exited earlier).
+ if (formatRule == nil)
+ {
+ formattedNumber = nationalFormat;
+ break;
+ }
+ // When the format we apply to this number doesn't contain national
+ // prefix, we can just return the national format.
+ // TODO: Refactor the code below with the code in
+ // isNationalPrefixPresentIfRequired.
+ NSString *candidateNationalPrefixRule = formatRule.nationalPrefixFormattingRule;
+ // We assume that the first-group symbol will never be _before_ the
+ // national prefix.
+ NSRange firstGroupRange = [candidateNationalPrefixRule rangeOfString:@"$1"];
+ if (firstGroupRange.location == NSNotFound)
+ {
+ formattedNumber = nationalFormat;
+ break;
+ }
+
+ if (firstGroupRange.location <= 0)
+ {
+ formattedNumber = nationalFormat;
+ break;
+ }
+ candidateNationalPrefixRule = [candidateNationalPrefixRule substringWithRange:NSMakeRange(0, firstGroupRange.location)];
+ candidateNationalPrefixRule = [self normalizeDigitsOnly:candidateNationalPrefixRule];
+ if (candidateNationalPrefixRule.length == 0)
+ {
+ // National prefix not used when formatting this number.
+ formattedNumber = nationalFormat;
+ break;
+ }
+ // Otherwise, we need to remove the national prefix from our output.
+ NBNumberFormat *numFormatCopy = [formatRule copy];
+ numFormatCopy.nationalPrefixFormattingRule = nil;
+ formattedNumber = [self formatByPattern:number numberFormat:NBEPhoneNumberFormatNATIONAL userDefinedFormats:@[numFormatCopy]];
+ break;
+ }
+ }
+
+ NSString *rawInput = number.rawInput;
+ // If no digit is inserted/removed/modified as a result of our formatting, we
+ // return the formatted phone number; otherwise we return the raw input the
+ // user entered.
+ if (formattedNumber != nil && rawInput.length > 0)
+ {
+ NSString *normalizedFormattedNumber = [self normalizeHelper:formattedNumber normalizationReplacements:_DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES];
+ /** @type {string} */
+ NSString *normalizedRawInput =[self normalizeHelper:rawInput normalizationReplacements:_DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES];
+
+ if ([normalizedFormattedNumber isEqualToString:normalizedRawInput] == NO)
+ {
+ formattedNumber = rawInput;
+ }
+ }
+ return formattedNumber;
+}
+
+
+/**
+ * Check if rawInput, which is assumed to be in the national format, has a
+ * national prefix. The national prefix is assumed to be in digits-only form.
+ * @param {string} rawInput
+ * @param {string} nationalPrefix
+ * @param {string} regionCode
+ * @return {boolean}
+ * @private
+ */
+- (BOOL)rawInputContainsNationalPrefix:(NSString*)rawInput nationalPrefix:(NSString*)nationalPrefix regionCode:(NSString*)regionCode
+{
+ NSString *normalizedNationalNumber = [self normalizeDigitsOnly:rawInput];
+ if ([self isStartingStringByRegex:normalizedNationalNumber regex:nationalPrefix])
+ {
+ @try {
+ // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the
+ // national prefix when written without it (e.g. 0777123) if we just do
+ // prefix matching. To tackle that, we check the validity of the number if
+ // the assumed national prefix is removed (777123 won't be valid in
+ // Japan).
+ NSString *subString = [normalizedNationalNumber substringFromIndex:nationalPrefix.length];
+ return [self isValidNumber:[self parse:subString defaultRegion:regionCode]];
+ }
+ @catch (NSException *ex) {
+ return NO;
+ }
+ }
+ return NO;
+}
+
+
+/**
+ * Returns NO if a number is from a region whose national significant number
+ * couldn't contain a leading zero, but has the italian_leading_zero field set
+ * to NO.
+ * @param {i18n.phonenumbers.PhoneNumber} number
+ * @return {boolean}
+ * @private
+ */
+- (BOOL)hasUnexpectedItalianLeadingZero:(NBPhoneNumber*)number
+{
+ return number.italianLeadingZero && [self isLeadingZeroPossible:number.countryCode] == NO;
+}
+
+
+/**
+ * @param {i18n.phonenumbers.PhoneNumber} number
+ * @return {boolean}
+ * @private
+ */
+- (BOOL)hasFormattingPatternForNumber:(NBPhoneNumber*)number
+{
+ NSNumber *countryCallingCode = number.countryCode;
+ NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCallingCode];
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:phoneNumberRegion];
+
+ if (metadata == nil)
+ {
+ return NO;
+ }
+
+ NSString *nationalNumber = [self getNationalSignificantNumber:number];
+ NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber];
+ return formatRule != nil;
+}
+
+
+/**
+ * Formats a phone number for out-of-country dialing purposes.
+ *
+ * Note that in this version, if the number was entered originally using alpha
+ * characters and this version of the number is stored in raw_input, this
+ * representation of the number will be used rather than the digit
+ * representation. Grouping information, as specified by characters such as '-'
+ * and ' ', will be retained.
+ *
+ * <p><b>Caveats:</b></p>
+ * <ul>
+ * <li>This will not produce good results if the country calling code is both
+ * present in the raw input _and_ is the start of the national number. This is
+ * not a problem in the regions which typically use alpha numbers.
+ * <li>This will also not produce good results if the raw input has any grouping
+ * information within the first three digits of the national number, and if the
+ * function needs to strip preceding digits/words in the raw input before these
+ * digits. Normally people group the first three digits together so this is not
+ * a huge problem - and will be fixed if it proves to be so.
+ * </ul>
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
+ * be formatted.
+ * @param {string} regionCallingFrom the region where the call is being placed.
+ * @return {string} the formatted phone number.
+ */
+- (NSString*)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError **)error
+{
+ NSString *res = nil;
+ @try {
+ res = [self formatOutOfCountryKeepingAlphaChars:number regionCallingFrom:regionCallingFrom];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NSString*)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom
+{
+ NSString *rawInput = number.rawInput;
+ // If there is no raw input, then we can't keep alpha characters because there
+ // aren't any. In this case, we return formatOutOfCountryCallingNumber.
+ if (rawInput == nil || rawInput.length == 0)
+ {
+ return [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
+ }
+
+ NSNumber *countryCode = number.countryCode;
+ if ([self hasValidCountryCallingCode:countryCode] == NO)
+ {
+ return rawInput;
+ }
+ // Strip any prefix such as country calling code, IDD, that was present. We do
+ // this by comparing the number in raw_input with the parsed number. To do
+ // this, first we normalize punctuation. We retain number grouping symbols
+ // such as ' ' only.
+ rawInput = [self normalizeHelper:rawInput normalizationReplacements:_ALL_PLUS_NUMBER_GROUPING_SYMBOLS_ removeNonMatches:NO];
+ //NSLog(@"---- formatOutOfCountryKeepingAlphaChars normalizeHelper rawInput [%@]", rawInput);
+ // Now we trim everything before the first three digits in the parsed number.
+ // We choose three because all valid alpha numbers have 3 digits at the start
+ // - if it does not, then we don't trim anything at all. Similarly, if the
+ // national number was less than three digits, we don't trim anything at all.
+ NSString *nationalNumber = [self getNationalSignificantNumber:number];
+ if (nationalNumber.length > 3)
+ {
+ NSInteger firstNationalNumberDigit = [self indexOfStringByString:rawInput target:[nationalNumber substringWithRange:NSMakeRange(0, 3)]];
+ if (firstNationalNumberDigit != -1)
+ {
+ rawInput = [rawInput substringFromIndex:firstNationalNumberDigit];
+ }
+ }
+
+ NBPhoneMetaData *metadataForRegionCallingFrom = [self getMetadataForRegion:regionCallingFrom];
+ if ([countryCode isEqualToNumber: @(NANPA_COUNTRY_CODE_)])
+ {
+ if ([self isNANPACountry:regionCallingFrom])
+ {
+ return [NSString stringWithFormat:@"%@ %@", countryCode, rawInput];
+ }
+ }
+ else if (metadataForRegionCallingFrom != nil && [countryCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCallingFrom]])
+ {
+ NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:metadataForRegionCallingFrom.numberFormats
+ nationalNumber:nationalNumber];
+ if (formattingPattern == nil)
+ {
+ // If no pattern above is matched, we format the original input.
+ return rawInput;
+ }
+
+ NBNumberFormat *newFormat = [formattingPattern copy];
+ // The first group is the first group of digits that the user wrote
+ // together.
+ newFormat.pattern = @"(\\d+)(.*)";
+ // Here we just concatenate them back together after the national prefix
+ // has been fixed.
+ newFormat.format = @"$1$2";
+ // Now we format using this pattern instead of the default pattern, but
+ // with the national prefix prefixed if necessary.
+ // This will not work in the cases where the pattern (and not the leading
+ // digits) decide whether a national prefix needs to be used, since we have
+ // overridden the pattern to match anything, but that is not the case in the
+ // metadata to date.
+
+ return [self formatNsnUsingPattern:rawInput formattingPattern:newFormat numberFormat:NBEPhoneNumberFormatNATIONAL carrierCode:nil];
+ }
+
+ NSString *internationalPrefixForFormatting = @"";
+ // If an unsupported region-calling-from is entered, or a country with
+ // multiple international prefixes, the international format of the number is
+ // returned, unless there is a preferred international prefix.
+ if (metadataForRegionCallingFrom != nil)
+ {
+ NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;
+ internationalPrefixForFormatting =
+ [self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX_ string:internationalPrefix] ? internationalPrefix : metadataForRegionCallingFrom.preferredInternationalPrefix;
+ }
+
+ NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
+ // Metadata cannot be nil because the country calling code is valid.
+ NBPhoneMetaData *metadataForRegion = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
+ NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadataForRegion numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
+ if (internationalPrefixForFormatting.length > 0)
+ {
+ return [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCode, rawInput, formattedExtension];
+ }
+ else
+ {
+ // Invalid region entered as country-calling-from (so no metadata was found
+ // for it) or the region chosen has multiple international dialling
+ // prefixes.
+ return [self prefixNumberWithCountryCallingCode:countryCode phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL formattedNationalNumber:rawInput formattedExtension:formattedExtension];
+ }
+}
+
+
+/**
+ * Note in some regions, the national number can be written in two completely
+ * different ways depending on whether it forms part of the NATIONAL format or
+ * INTERNATIONAL format. The numberFormat parameter here is used to specify
+ * which format to use for those cases. If a carrierCode is specified, this will
+ * be inserted into the formatted string to replace $CC.
+ *
+ * @param {string} number a string of characters representing a phone number.
+ * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
+ * region that we think this number is from.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @param {string=} opt_carrierCode
+ * @return {string} the formatted phone number.
+ * @private
+ */
+- (NSString*)formatNsn:(NSString*)phoneNumber metadata:(NBPhoneMetaData*)metadata phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat carrierCode:(NSString*)opt_carrierCode
+{
+ NSMutableArray *intlNumberFormats = metadata.intlNumberFormats;
+ // When the intlNumberFormats exists, we use that to format national number
+ // for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
+ NSArray *availableFormats = ([intlNumberFormats count] <= 0 || numberFormat == NBEPhoneNumberFormatNATIONAL) ? metadata.numberFormats : intlNumberFormats;
+ NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:availableFormats nationalNumber:phoneNumber];
+
+ if (formattingPattern == nil)
+ {
+ return phoneNumber;
+ }
+
+ return [self formatNsnUsingPattern:phoneNumber formattingPattern:formattingPattern numberFormat:numberFormat carrierCode:opt_carrierCode];
+}
+
+
+/**
+ * @param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the
+ * available formats the phone number could be formatted into.
+ * @param {string} nationalNumber a string of characters representing a phone
+ * number.
+ * @return {i18n.phonenumbers.NumberFormat}
+ * @private
+ */
+- (NBNumberFormat*)chooseFormattingPatternForNumber:(NSArray*)availableFormats nationalNumber:(NSString*)nationalNumber
+{
+ for (NBNumberFormat *numFormat in availableFormats)
+ {
+ NSUInteger size = [numFormat.leadingDigitsPatterns count];
+ // We always use the last leading_digits_pattern, as it is the most detailed.
+ if (size == 0 || [self stringPositionByRegex:nationalNumber regex:[numFormat.leadingDigitsPatterns lastObject]] == 0)
+ {
+ if ([self matchesEntirely:numFormat.pattern string:nationalNumber])
+ {
+ return numFormat;
+ }
+ }
+ }
+
+ return nil;
+}
+
+
+/**
+ * Note that carrierCode is optional - if nil or an empty string, no carrier
+ * code replacement will take place.
+ *
+ * @param {string} nationalNumber a string of characters representing a phone
+ * number.
+ * @param {i18n.phonenumbers.NumberFormat} formattingPattern the formatting rule
+ * the phone number should be formatted into.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @param {string=} opt_carrierCode
+ * @return {string} the formatted phone number.
+ * @private
+ */
+- (NSString*)formatNsnUsingPattern:(NSString*)nationalNumber formattingPattern:(NBNumberFormat*)formattingPattern numberFormat:(NBEPhoneNumberFormat)numberFormat carrierCode:(NSString*)opt_carrierCode
+{
+ NSString *numberFormatRule = formattingPattern.format;
+ NSString *domesticCarrierCodeFormattingRule = formattingPattern.domesticCarrierCodeFormattingRule;
+ NSString *formattedNationalNumber = @"";
+
+ if (numberFormat == NBEPhoneNumberFormatNATIONAL && [self hasValue:opt_carrierCode] && domesticCarrierCodeFormattingRule.length > 0)
+ {
+ // Replace the $CC in the formatting rule with the desired carrier code.
+ NSString *carrierCodeFormattingRule = [self replaceStringByRegex:domesticCarrierCodeFormattingRule regex:CC_PATTERN_ withTemplate:opt_carrierCode];
+ // Now replace the $FG in the formatting rule with the first group and
+ // the carrier code combined in the appropriate way.
+ numberFormatRule = [self replaceFirstStringByRegex:numberFormatRule regex:FIRST_GROUP_PATTERN_
+ withTemplate:carrierCodeFormattingRule];
+ formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:numberFormatRule];
+ }
+ else
+ {
+ // Use the national prefix formatting rule instead.
+ NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;
+ if (numberFormat == NBEPhoneNumberFormatNATIONAL && [self hasValue:nationalPrefixFormattingRule])
+ {
+ NSString *replacePattern = [self replaceFirstStringByRegex:numberFormatRule regex:FIRST_GROUP_PATTERN_ withTemplate:nationalPrefixFormattingRule];
+ formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:replacePattern];
+ }
+ else
+ {
+ formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:numberFormatRule];
+ }
+ }
+
+ if (numberFormat == NBEPhoneNumberFormatRFC3966)
+ {
+ // Strip any leading punctuation.
+ formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber regex:[NSString stringWithFormat:@"^%@", self.SEPARATOR_PATTERN_] withTemplate:@""];
+
+ // Replace the rest with a dash between each number group.
+ formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber regex:self.SEPARATOR_PATTERN_ withTemplate:@"-"];
+ }
+ return formattedNationalNumber;
+}
+
+
+/**
+ * Gets a valid number for the specified region.
+ *
+ * @param {string} regionCode the region for which an example number is needed.
+ * @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the
+ * specified region. Returns nil when the metadata does not contain such
+ * information, or the region 001 is passed in. For 001 (representing non-
+ * geographical numbers), call {@link #getExampleNumberForNonGeoEntity}
+ * instead.
+ */
+- (NBPhoneNumber*)getExampleNumber:(NSString*)regionCode error:(NSError *__autoreleasing *)error
+{
+ NBPhoneNumber *res = nil;
+ @try {
+ res = [self getExampleNumber:regionCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NBPhoneNumber*)getExampleNumber:(NSString*)regionCode
+{
+ return [self getExampleNumberForType:regionCode type:NBEPhoneNumberTypeFIXED_LINE];
+}
+
+
+/**
+ * Gets a valid number for the specified region and number type.
+ *
+ * @param {string} regionCode the region for which an example number is needed.
+ * @param {i18n.phonenumbers.PhoneNumberType} type the type of number that is
+ * needed.
+ * @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified
+ * region and type. Returns nil when the metadata does not contain such
+ * information or if an invalid region or region 001 was entered.
+ * For 001 (representing non-geographical numbers), call
+ * {@link #getExampleNumberForNonGeoEntity} instead.
+ */
+- (NBPhoneNumber*)getExampleNumberForType:(NSString*)regionCode type:(NBEPhoneNumberType)type error:(NSError *__autoreleasing *)error
+{
+ NBPhoneNumber *res = nil;
+ @try {
+ res = [self getExampleNumberForType:regionCode type:type];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NBPhoneNumber*)getExampleNumberForType:(NSString*)regionCode type:(NBEPhoneNumberType)type
+{
+ // Check the region code is valid.
+ if ([self isValidRegionCode:regionCode] == NO)
+ {
+ return nil;
+ }
+
+ NBPhoneNumberDesc *desc = [self getNumberDescByType:[self getMetadataForRegion:regionCode] type:type];
+
+ @try {
+ if ([self hasValue:desc.exampleNumber ])
+ {
+ return [self parse:desc.exampleNumber defaultRegion:regionCode];
+ }
+ }
+ @catch (NSException *e)
+ {
+ }
+
+ return nil;
+}
+
+
+/**
+ * Gets a valid number for the specified country calling code for a
+ * non-geographical entity.
+ *
+ * @param {number} countryCallingCode the country calling code for a
+ * non-geographical entity.
+ * @return {i18n.phonenumbers.PhoneNumber} a valid number for the
+ * non-geographical entity. Returns nil when the metadata does not contain
+ * such information, or the country calling code passed in does not belong
+ * to a non-geographical entity.
+ */
+- (NBPhoneNumber*)getExampleNumberForNonGeoEntity:(NSNumber*)countryCallingCode error:(NSError *__autoreleasing *)error
+{
+ NBPhoneNumber *res = nil;
+ @try {
+ res = [self getExampleNumberForNonGeoEntity:countryCallingCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (NBPhoneNumber*)getExampleNumberForNonGeoEntity:(NSNumber*)countryCallingCode
+{
+ NBPhoneMetaData *metadata = [self getMetadataForNonGeographicalRegion:countryCallingCode];
+
+ if (metadata != nil)
+ {
+ NBPhoneNumberDesc *desc = metadata.generalDesc;
+ @try {
+ if ([self hasValue:desc.exampleNumber])
+ {
+ NSString *callCode = [NSString stringWithFormat:@"+%@%@", countryCallingCode, desc.exampleNumber];
+ return [self parse:callCode defaultRegion:UNKNOWN_REGION_];
+ }
+ }
+ @catch (NSException *e) {
+ }
+ }
+ return nil;
+}
+
+
+/**
+ * Gets the formatted extension of a phone number, if the phone number had an
+ * extension specified. If not, it returns an empty string.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have
+ * an extension.
+ * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
+ * region that we think this number is from.
+ * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
+ * phone number should be formatted into.
+ * @return {string} the formatted extension if any.
+ * @private
+ */
+- (NSString*)maybeGetFormattedExtension:(NBPhoneNumber*)number metadata:(NBPhoneMetaData*)metadata numberFormat:(NBEPhoneNumberFormat)numberFormat
+{
+ if ([self hasValue:number.extension] == NO)
+ {
+ return @"";
+ }
+ else
+ {
+ if (numberFormat == NBEPhoneNumberFormatRFC3966)
+ {
+ return [NSString stringWithFormat:@"%@%@", RFC3966_EXTN_PREFIX_, number.extension];
+ }
+ else
+ {
+ if ([self hasValue:metadata.preferredExtnPrefix])
+ {
+ return [NSString stringWithFormat:@"%@%@", metadata.preferredExtnPrefix, number.extension];
+ }
+ else
+ {
+ return [NSString stringWithFormat:@"%@%@", DEFAULT_EXTN_PREFIX_, number.extension];
+ }
+ }
+ }
+}
+
+
+/**
+ * @param {i18n.phonenumbers.PhoneMetadata} metadata
+ * @param {i18n.phonenumbers.PhoneNumberType} type
+ * @return {i18n.phonenumbers.PhoneNumberDesc}
+ * @private
+ */
+- (NBPhoneNumberDesc*)getNumberDescByType:(NBPhoneMetaData*)metadata type:(NBEPhoneNumberType)type
+{
+ switch (type)
+ {
+ case NBEPhoneNumberTypePREMIUM_RATE:
+ return metadata.premiumRate;
+ case NBEPhoneNumberTypeTOLL_FREE:
+ return metadata.tollFree;
+ case NBEPhoneNumberTypeMOBILE:
+ if (metadata.mobile == nil) return metadata.generalDesc;
+ return metadata.mobile;
+ case NBEPhoneNumberTypeFIXED_LINE:
+ case NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE:
+ if (metadata.fixedLine == nil) return metadata.generalDesc;
+ return metadata.fixedLine;
+ case NBEPhoneNumberTypeSHARED_COST:
+ return metadata.sharedCost;
+ case NBEPhoneNumberTypeVOIP:
+ return metadata.voip;
+ case NBEPhoneNumberTypePERSONAL_NUMBER:
+ return metadata.personalNumber;
+ case NBEPhoneNumberTypePAGER:
+ return metadata.pager;
+ case NBEPhoneNumberTypeUAN:
+ return metadata.uan;
+ case NBEPhoneNumberTypeVOICEMAIL:
+ return metadata.voicemail;
+ default:
+ return metadata.generalDesc;
+ }
+}
+
+/**
+ * Gets the type of a phone number.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
+ * to know the type.
+ * @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number.
+ */
+- (NBEPhoneNumberType)getNumberType:(NBPhoneNumber*)phoneNumber
+{
+ NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:phoneNumber.countryCode regionCode:regionCode];
+ if (metadata == nil)
+ {
+ return NBEPhoneNumberTypeUNKNOWN;
+ }
+
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];
+ return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata];
+}
+
+
+/**
+ * @param {string} nationalNumber
+ * @param {i18n.phonenumbers.PhoneMetadata} metadata
+ * @return {i18n.phonenumbers.PhoneNumberType}
+ * @private
+ */
+- (NBEPhoneNumberType)getNumberTypeHelper:(NSString*)nationalNumber metadata:(NBPhoneMetaData*)metadata
+{
+ NBPhoneNumberDesc *generalNumberDesc = metadata.generalDesc;
+
+ //NSLog(@"getNumberTypeHelper - UNKNOWN 1");
+ if ([self hasValue:generalNumberDesc.nationalNumberPattern] == NO ||
+ [self isNumberMatchingDesc:nationalNumber numberDesc:generalNumberDesc] == NO)
+ {
+ //NSLog(@"getNumberTypeHelper - UNKNOWN 2");
+ return NBEPhoneNumberTypeUNKNOWN;
+ }
+
+ //NSLog(@"getNumberTypeHelper - PREMIUM_RATE 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.premiumRate])
+ {
+ //NSLog(@"getNumberTypeHelper - PREMIUM_RATE 2");
+ return NBEPhoneNumberTypePREMIUM_RATE;
+ }
+
+ //NSLog(@"getNumberTypeHelper - TOLL_FREE 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.tollFree])
+ {
+ //NSLog(@"getNumberTypeHelper - TOLL_FREE 2");
+ return NBEPhoneNumberTypeTOLL_FREE;
+ }
+
+ //NSLog(@"getNumberTypeHelper - SHARED_COST 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.sharedCost])
+ {
+ //NSLog(@"getNumberTypeHelper - SHARED_COST 2");
+ return NBEPhoneNumberTypeSHARED_COST;
+ }
+
+ //NSLog(@"getNumberTypeHelper - VOIP 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voip])
+ {
+ //NSLog(@"getNumberTypeHelper - VOIP 2");
+ return NBEPhoneNumberTypeVOIP;
+ }
+
+ //NSLog(@"getNumberTypeHelper - PERSONAL_NUMBER 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.personalNumber])
+ {
+ //NSLog(@"getNumberTypeHelper - PERSONAL_NUMBER 2");
+ return NBEPhoneNumberTypePERSONAL_NUMBER;
+ }
+
+ //NSLog(@"getNumberTypeHelper - PAGER 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.pager])
+ {
+ //NSLog(@"getNumberTypeHelper - PAGER 2");
+ return NBEPhoneNumberTypePAGER;
+ }
+
+ //NSLog(@"getNumberTypeHelper - UAN 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.uan])
+ {
+ //NSLog(@"getNumberTypeHelper - UAN 2");
+ return NBEPhoneNumberTypeUAN;
+ }
+
+ //NSLog(@"getNumberTypeHelper - VOICEMAIL 1");
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voicemail])
+ {
+ //NSLog(@"getNumberTypeHelper - VOICEMAIL 2");
+ return NBEPhoneNumberTypeVOICEMAIL;
+ }
+
+ if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.fixedLine])
+ {
+ if (metadata.sameMobileAndFixedLinePattern)
+ {
+ //NSLog(@"getNumberTypeHelper - FIXED_LINE_OR_MOBILE");
+ return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
+ }
+ else if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile])
+ {
+ //NSLog(@"getNumberTypeHelper - FIXED_LINE_OR_MOBILE");
+ return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
+ }
+ //NSLog(@"getNumberTypeHelper - FIXED_LINE");
+ return NBEPhoneNumberTypeFIXED_LINE;
+ }
+
+ // Otherwise, test to see if the number is mobile. Only do this if certain
+ // that the patterns for mobile and fixed line aren't the same.
+ if ([metadata sameMobileAndFixedLinePattern] == NO && [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile])
+ {
+ return NBEPhoneNumberTypeMOBILE;
+ }
+
+ return NBEPhoneNumberTypeUNKNOWN;
+}
+
+
+/**
+ * Returns the metadata for the given region code or {@code nil} if the region
+ * code is invalid or unknown.
+ *
+ * @param {?string} regionCode
+ * @return {i18n.phonenumbers.PhoneMetadata}
+ */
+- (NBPhoneMetaData*)getMetadataForRegion:(NSString*)regionCode
+{
+ if ([self hasValue:regionCode] == NO)
+ {
+ return nil;
+ }
+
+ regionCode = [regionCode uppercaseString];
+
+ NBPhoneMetaData *metadata = [self.coreMetaData objectForKey:regionCode];
+
+ return metadata;
+}
+
+
+/**
+ * @param {number} countryCallingCode
+ * @return {i18n.phonenumbers.PhoneMetadata}
+ */
+- (NBPhoneMetaData*)getMetadataForNonGeographicalRegion:(NSNumber*)countryCallingCode
+{
+ NSString *countryCallingCodeStr = [NSString stringWithFormat:@"%@", countryCallingCode];
+ return [self getMetadataForRegion:countryCallingCodeStr];
+}
+
+
+/**
+ * @param {string} nationalNumber
+ * @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
+ * @return {boolean}
+ * @private
+ */
+- (BOOL)isNumberMatchingDesc:(NSString*)nationalNumber numberDesc:(NBPhoneNumberDesc*)numberDesc
+{
+ if (numberDesc == nil)
+ return NO;
+
+ if ([self hasValue:numberDesc.possibleNumberPattern] == NO || [numberDesc.possibleNumberPattern isEqual:@"NA"])
+ return [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber];
+
+ if ([self hasValue:numberDesc.nationalNumberPattern] == NO || [numberDesc.nationalNumberPattern isEqual:@"NA"])
+ return [self matchesEntirely:numberDesc.possibleNumberPattern string:nationalNumber];
+
+ return [self matchesEntirely:numberDesc.possibleNumberPattern string:nationalNumber] &&
+ [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber];
+}
+
+
+/**
+ * Tests whether a phone number matches a valid pattern. Note this doesn't
+ * verify the number is actually in use, which is impossible to tell by just
+ * looking at a number itself.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
+ * to validate.
+ * @return {boolean} a boolean that indicates whether the number is of a valid
+ * pattern.
+ */
+- (BOOL)isValidNumber:(NBPhoneNumber*)number
+{
+ NSString *regionCode = [self getRegionCodeForNumber:number];
+ return [self isValidNumberForRegion:number regionCode:regionCode];
+}
+
+
+/**
+ * Tests whether a phone number is valid for a certain region. Note this doesn't
+ * verify the number is actually in use, which is impossible to tell by just
+ * looking at a number itself. If the country calling code is not the same as
+ * the country calling code for the region, this immediately exits with NO.
+ * After this, the specific number pattern rules for the region are examined.
+ * This is useful for determining for example whether a particular number is
+ * valid for Canada, rather than just a valid NANPA number.
+ * Warning: In most cases, you want to use {@link #isValidNumber} instead. For
+ * example, this method will mark numbers from British Crown dependencies such
+ * as the Isle of Man as invalid for the region "GB" (United Kingdom), since it
+ * has its own region code, "IM", which may be undesirable.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
+ * to validate.
+ * @param {?string} regionCode the region that we want to validate the phone
+ * number for.
+ * @return {boolean} a boolean that indicates whether the number is of a valid
+ * pattern.
+ */
+- (BOOL)isValidNumberForRegion:(NBPhoneNumber*)number regionCode:(NSString*)regionCode
+{
+ NSNumber *countryCode = number.countryCode;
+
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
+ if (metadata == nil ||
+ ([_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] == NO &&
+ ![countryCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCode]]))
+ {
+ // Either the region code was invalid, or the country calling code for this
+ // number does not match that of the region code.
+ return NO;
+ }
+
+ NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc;
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
+
+ // For regions where we don't have metadata for PhoneNumberDesc, we treat any
+ // number passed in as a valid number if its national significant number is
+ // between the minimum and maximum lengths defined by ITU for a national
+ // significant number.
+ if ([self hasValue:generalNumDesc.nationalNumberPattern] == NO)
+ {
+ NSUInteger numberLength = nationalSignificantNumber.length;
+ return numberLength > MIN_LENGTH_FOR_NSN_ && numberLength <= MAX_LENGTH_FOR_NSN_;
+ }
+
+ return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata] != NBEPhoneNumberTypeUNKNOWN;
+}
+
+
+/**
+ * Returns the region where a phone number is from. This could be used for
+ * geocoding at the region level.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin
+ * we want to know.
+ * @return {?string} the region where the phone number is from, or nil
+ * if no region matches this calling code.
+ */
+- (NSString*)getRegionCodeForNumber:(NBPhoneNumber*)phoneNumber
+{
+ if (phoneNumber == nil)
+ {
+ return nil;
+ }
+
+ NSArray *regionCodes = [self regionCodeFromCountryCode:phoneNumber.countryCode];
+ if (regionCodes == nil || [regionCodes count] <= 0)
+ {
+ return nil;
+ }
+
+ if ([regionCodes count] == 1)
+ {
+ return [regionCodes objectAtIndex:0];
+ }
+ else
+ {
+ return [self getRegionCodeForNumberFromRegionList:phoneNumber regionCodes:regionCodes];
+ }
+}
+
+
+/**
+ * @param {i18n.phonenumbers.PhoneNumber} number
+ * @param {Array.<string>} regionCodes
+ * @return {?string}
+ * @private
+
+ */
+- (NSString*)getRegionCodeForNumberFromRegionList:(NBPhoneNumber*)phoneNumber regionCodes:(NSArray*)regionCodes
+{
+ NSString *nationalNumber = [self getNationalSignificantNumber:phoneNumber];
+ NSUInteger regionCodesCount = [regionCodes count];
+
+ for (NSUInteger i = 0; i<regionCodesCount; i++)
+ {
+ NSString *regionCode = [regionCodes objectAtIndex:i];
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode];
+
+ if ([self hasValue:metadata.leadingDigits])
+ {
+ if ([self stringPositionByRegex:nationalNumber regex:metadata.leadingDigits] == 0)
+ {
+ return regionCode;
+ }
+ }
+ else if ([self getNumberTypeHelper:nationalNumber metadata:metadata] != NBEPhoneNumberTypeUNKNOWN)
+ {
+ return regionCode;
+ }
+ }
+
+ return nil;
+}
+
+
+/**
+ * Returns the region code that matches the specific country calling code. In
+ * the case of no region code being found, ZZ will be returned. In the case of
+ * multiple regions, the one designated in the metadata as the 'main' region for
+ * this calling code will be returned.
+ *
+ * @param {number} countryCallingCode the country calling code.
+ * @return {string}
+ */
+- (NSString*)getRegionCodeForCountryCode:(NSNumber*)countryCallingCode
+{
+ NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode];
+ return regionCodes == nil ? UNKNOWN_REGION_ : [regionCodes objectAtIndex:0];
+}
+
+
+/**
+ * Returns a list with the region codes that match the specific country calling
+ * code. For non-geographical country calling codes, the region code 001 is
+ * returned. Also, in the case of no region code being found, an empty list is
+ * returned.
+ *
+ * @param {number} countryCallingCode the country calling code.
+ * @return {Array.<string>}
+ */
+- (NSArray*)getRegionCodesForCountryCode:(NSNumber*)countryCallingCode
+{
+ NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode];
+ return regionCodes == nil ? nil : regionCodes;
+}
+
+
+/**
+ * Returns the country calling code for a specific region. For example, this
+ * would be 1 for the United States, and 64 for New Zealand.
+ *
+ * @param {?string} regionCode the region that we want to get the country
+ * calling code for.
+ * @return {number} the country calling code for the region denoted by
+ * regionCode.
+ */
+- (NSNumber*)getCountryCodeForRegion:(NSString*)regionCode
+{
+ if ([self isValidRegionCode:regionCode] == NO)
+ {
+ return @0;
+ }
+
+ NSError *error = nil;
+ NSNumber *res = [self getCountryCodeForValidRegion:regionCode error:&error];
+ if (error != nil)
+ return @0;
+ return res;
+}
+
+
+/**
+ * Returns the country calling code for a specific region. For example, this
+ * would be 1 for the United States, and 64 for New Zealand. Assumes the region
+ * is already valid.
+ *
+ * @param {?string} regionCode the region that we want to get the country
+ * calling code for.
+ * @return {number} the country calling code for the region denoted by
+ * regionCode.
+ * @throws {string} if the region is invalid
+ * @private
+ */
+- (NSNumber *)getCountryCodeForValidRegion:(NSString*)regionCode error:(NSError**)error
+{
+ NSNumber * res = nil;
+ @try {
+ res = [self getCountryCodeForValidRegion:regionCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NSNumber *)getCountryCodeForValidRegion:(NSString*)regionCode
+{
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode];
+ if (metadata == nil)
+ {
+ NSException* metaException = [NSException exceptionWithName:@"INVALID_REGION_CODE"
+ reason:[NSString stringWithFormat:@"Invalid region code:%@", regionCode]
+ userInfo:nil];
+ @throw metaException;
+ }
+ return metadata.countryCode;
+}
+
+
+/**
+ * Returns the national dialling prefix for a specific region. For example, this
+ * would be 1 for the United States, and 0 for New Zealand. Set stripNonDigits
+ * to NO to strip symbols like '~' (which indicates a wait for a dialling
+ * tone) from the prefix returned. If no national prefix is present, we return
+ * nil.
+ *
+ * <p>Warning: Do not use this method for do-your-own formatting - for some
+ * regions, the national dialling prefix is used only for certain types of
+ * numbers. Use the library's formatting functions to prefix the national prefix
+ * when required.
+ *
+ * @param {?string} regionCode the region that we want to get the dialling
+ * prefix for.
+ * @param {boolean} stripNonDigits NO to strip non-digits from the national
+ * dialling prefix.
+ * @return {?string} the dialling prefix for the region denoted by
+ * regionCode.
+ */
+- (NSString*)getNddPrefixForRegion:(NSString*)regionCode stripNonDigits:(BOOL)stripNonDigits
+{
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode];
+ if (metadata == nil)
+ {
+ return nil;
+ }
+
+ NSString *nationalPrefix = metadata.nationalPrefix;
+ // If no national prefix was found, we return nil.
+ if (nationalPrefix.length == 0)
+ {
+ return nil;
+ }
+
+ if (stripNonDigits)
+ {
+ // Note: if any other non-numeric symbols are ever used in national
+ // prefixes, these would have to be removed here as well.
+ nationalPrefix = [nationalPrefix stringByReplacingOccurrencesOfString:@"~" withString:@""];
+ }
+ return nationalPrefix;
+}
+
+
+/**
+ * Checks if this is a region under the North American Numbering Plan
+ * Administration (NANPA).
+ *
+ * @param {?string} regionCode the ISO 3166-1 two-letter region code.
+ * @return {boolean} NO if regionCode is one of the regions under NANPA.
+ */
+- (BOOL)isNANPACountry:(NSString*)regionCode
+{
+ BOOL isExists = NO;
+
+ NSArray *res = [self regionCodeFromCountryCode:@(NANPA_COUNTRY_CODE_)];
+ for (NSString *inRegionCode in res)
+ {
+ if ([inRegionCode isEqualToString:regionCode.uppercaseString])
+ {
+ isExists = YES;
+ }
+ }
+
+ return regionCode != nil && isExists;
+}
+
+
+/**
+ * Checks whether countryCode represents the country calling code from a region
+ * whose national significant number could contain a leading zero. An example of
+ * such a region is Italy. Returns NO if no metadata for the country is
+ * found.
+ *
+ * @param {number} countryCallingCode the country calling code.
+ * @return {boolean}
+ */
+- (BOOL)isLeadingZeroPossible:(NSNumber*)countryCallingCode
+{
+ NBPhoneMetaData *mainMetadataForCallingCode = [self getMetadataForRegionOrCallingCode:countryCallingCode
+ regionCode:[self getRegionCodeForCountryCode:countryCallingCode]];
+
+ return mainMetadataForCallingCode != nil && mainMetadataForCallingCode.leadingZeroPossible;
+}
+
+
+/**
+ * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT.
+ * A valid vanity number will start with at least 3 digits and will have three
+ * or more alpha characters. This does not do region-specific checks - to work
+ * out if this number is actually valid for a region, it should be parsed and
+ * methods such as {@link #isPossibleNumberWithReason} and
+ * {@link #isValidNumber} should be used.
+ *
+ * @param {string} number the number that needs to be checked.
+ * @return {boolean} NO if the number is a valid vanity number.
+ */
+- (BOOL)isAlphaNumber:(NSString*)number
+{
+ if ([self isViablePhoneNumber:number] == NO)
+ {
+ // Number is too short, or doesn't match the basic phone number pattern.
+ return NO;
+ }
+
+ /** @type {!goog.string.StringBuffer} */
+ NSString *strippedNumber = [number copy];
+ [self maybeStripExtension:&strippedNumber];
+
+ return [self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:strippedNumber];
+}
+
+
+/**
+ * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of
+ * returning the reason for failure, this method returns a boolean value.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
+ * checked.
+ * @return {boolean} NO if the number is possible.
+ */
+- (BOOL)isPossibleNumber:(NBPhoneNumber*)number error:(NSError**)error
+{
+ BOOL res = NO;
+ @try {
+ res = [self isPossibleNumber:number];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (BOOL)isPossibleNumber:(NBPhoneNumber*)number
+{
+ return [self isPossibleNumberWithReason:number] == NBEValidationResultIS_POSSIBLE;
+}
+
+
+/**
+ * Helper method to check a number against a particular pattern and determine
+ * whether it matches, or is too short or too long. Currently, if a number
+ * pattern suggests that numbers of length 7 and 10 are possible, and a number
+ * in between these possible lengths is entered, such as of length 8, this will
+ * return TOO_LONG.
+ *
+ * @param {string} numberPattern
+ * @param {string} number
+ * @return {ValidationResult}
+ * @private
+ */
+- (NBEValidationResult)testNumberLengthAgainstPattern:(NSString*)numberPattern number:(NSString*)number
+{
+ if ([self matchesEntirely:numberPattern string:number])
+ {
+ return NBEValidationResultIS_POSSIBLE;
+ }
+
+ if ([self stringPositionByRegex:number regex:numberPattern] == 0)
+ {
+ return NBEValidationResultTOO_LONG;
+ }
+ else
+ {
+ return NBEValidationResultTOO_SHORT;
+ }
+}
+
+
+/**
+ * Check whether a phone number is a possible number. It provides a more lenient
+ * check than {@link #isValidNumber} in the following sense:
+ * <ol>
+ * <li>It only checks the length of phone numbers. In particular, it doesn't
+ * check starting digits of the number.
+ * <li>It doesn't attempt to figure out the type of the number, but uses general
+ * rules which applies to all types of phone numbers in a region. Therefore, it
+ * is much faster than isValidNumber.
+ * <li>For fixed line numbers, many regions have the concept of area code, which
+ * together with subscriber number constitute the national significant number.
+ * It is sometimes okay to dial the subscriber number only when dialing in the
+ * same area. This function will return NO if the subscriber-number-only
+ * version is passed in. On the other hand, because isValidNumber validates
+ * using information on both starting digits (for fixed line numbers, that would
+ * most likely be area codes) and length (obviously includes the length of area
+ * codes for fixed line numbers), it will return NO for the
+ * subscriber-number-only version.
+ * </ol>
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
+ * checked.
+ * @return {ValidationResult} a
+ * ValidationResult object which indicates whether the number is possible.
+ */
+- (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber*)number error:(NSError *__autoreleasing *)error
+{
+ NBEValidationResult res = -1;
+ @try {
+ res = [self isPossibleNumberWithReason:number];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return res;
+}
+
+
+- (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber*)number
+{
+ NSString *nationalNumber = [self getNationalSignificantNumber:number];
+ NSNumber *countryCode = number.countryCode;
+ // Note: For Russian Fed and NANPA numbers, we just use the rules from the
+ // default region (US or Russia) since the getRegionCodeForNumber will not
+ // work if the number is possible but not valid. This would need to be
+ // revisited if the possible number pattern ever differed between various
+ // regions within those plans.
+ if ([self hasValidCountryCallingCode:countryCode] == NO)
+ {
+ return NBEValidationResultINVALID_COUNTRY_CODE;
+ }
+
+ NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
+ // Metadata cannot be nil because the country calling code is valid.
+ NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
+ NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc;
+
+ // Handling case of numbers with no metadata.
+ if ([self hasValue:generalNumDesc.nationalNumberPattern] == NO)
+ {
+ NSUInteger numberLength = nationalNumber.length;
+
+ if (numberLength < MIN_LENGTH_FOR_NSN_)
+ {
+ return NBEValidationResultTOO_SHORT;
+ }
+ else if (numberLength > MAX_LENGTH_FOR_NSN_)
+ {
+ return NBEValidationResultTOO_LONG;
+ }
+ else
+ {
+ return NBEValidationResultIS_POSSIBLE;
+ }
+ }
+
+ NSString *possibleNumberPattern = generalNumDesc.possibleNumberPattern;
+ return [self testNumberLengthAgainstPattern:possibleNumberPattern number:nationalNumber];
+}
+
+
+/**
+ * Check whether a phone number is a possible number given a number in the form
+ * of a string, and the region where the number could be dialed from. It
+ * provides a more lenient check than {@link #isValidNumber}. See
+ * {@link #isPossibleNumber} for details.
+ *
+ * <p>This method first parses the number, then invokes
+ * {@link #isPossibleNumber} with the resultant PhoneNumber object.
+ *
+ * @param {string} number the number that needs to be checked, in the form of a
+ * string.
+ * @param {string} regionDialingFrom the region that we are expecting the number
+ * to be dialed from.
+ * Note this is different from the region where the number belongs.
+ * For example, the number +1 650 253 0000 is a number that belongs to US.
+ * When written in this form, it can be dialed from any region. When it is
+ * written as 00 1 650 253 0000, it can be dialed from any region which uses
+ * an international dialling prefix of 00. When it is written as
+ * 650 253 0000, it can only be dialed from within the US, and when written
+ * as 253 0000, it can only be dialed from within a smaller area in the US
+ * (Mountain View, CA, to be more specific).
+ * @return {boolean} NO if the number is possible.
+ */
+- (BOOL)isPossibleNumberString:(NSString*)number regionDialingFrom:(NSString*)regionDialingFrom error:(NSError**)error
+{
+ BOOL res = NO;
+ @try {
+ res = [self isPossibleNumberString:number regionDialingFrom:regionDialingFrom];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (BOOL)isPossibleNumberString:(NSString*)number regionDialingFrom:(NSString*)regionDialingFrom
+{
+ @try {
+ return [self isPossibleNumber:[self parse:number defaultRegion:regionDialingFrom]];
+ }
+ @catch (NSException *e) {
+ return NO;
+ }
+}
+
+
+/**
+ * Attempts to extract a valid number from a phone number that is too long to be
+ * valid, and resets the PhoneNumber object passed in to that valid version. If
+ * no valid number could be extracted, the PhoneNumber object passed in will not
+ * be modified.
+ * @param {i18n.phonenumbers.PhoneNumber} number a PhoneNumber object which
+ * contains a number that is too long to be valid.
+ * @return {boolean} NO if a valid phone number can be successfully extracted.
+ */
+- (BOOL)truncateTooLongNumber:(NBPhoneNumber*)number error:(NSError**)error
+{
+ BOOL res = NO;
+ @try {
+ res = [self truncateTooLongNumber:number];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (BOOL)truncateTooLongNumber:(NBPhoneNumber*)number
+{
+ if ([self isValidNumber:number])
+ {
+ return YES;
+ }
+
+ NBPhoneNumber *numberCopy = [number copy];
+ NSNumber *nationalNumber = number.nationalNumber;
+ do {
+ nationalNumber = [NSNumber numberWithLongLong:(long long)floor(nationalNumber.unsignedLongLongValue / 10)];
+ numberCopy.nationalNumber = [nationalNumber copy];
+ if ([nationalNumber isEqualToNumber:@0] || [self isPossibleNumberWithReason:numberCopy] == NBEValidationResultTOO_SHORT) {
+ return NO;
+ }
+ }
+ while ([self isValidNumber:numberCopy] == NO);
+
+ number.nationalNumber = nationalNumber;
+ return YES;
+}
+
+
+/**
+ * Extracts country calling code from fullNumber, returns it and places the
+ * remaining number in nationalNumber. It assumes that the leading plus sign or
+ * IDD has already been removed. Returns 0 if fullNumber doesn't start with a
+ * valid country calling code, and leaves nationalNumber unmodified.
+ *
+ * @param {!goog.string.StringBuffer} fullNumber
+ * @param {!goog.string.StringBuffer} nationalNumber
+ * @return {number}
+ */
+- (NSNumber*)extractCountryCode:(NSString*)fullNumber nationalNumber:(NSString**)nationalNumber
+{
+ if ((fullNumber.length == 0) || ([[fullNumber substringToIndex:1] isEqualToString:@"0"]))
+ {
+ // Country codes do not begin with a '0'.
+ return @0;
+ }
+
+ NSUInteger numberLength = fullNumber.length;
+
+ for (NSUInteger i = 1; i <= MAX_LENGTH_COUNTRY_CODE_ && i <= numberLength; ++i)
+ {
+ NSString *subNumber = [fullNumber substringWithRange:NSMakeRange(0, i)];
+ NSNumber *potentialCountryCode = [NSNumber numberWithInteger:[subNumber integerValue]];
+
+ NSArray *regionCodes = [self regionCodeFromCountryCode:potentialCountryCode];
+ if (regionCodes != nil && regionCodes.count > 0)
+ {
+ if (nationalNumber != NULL)
+ {
+ if ((*nationalNumber) == nil)
+ (*nationalNumber) = [NSString stringWithFormat:@"%@", [fullNumber substringFromIndex:i]];
+ else
+ (*nationalNumber) = [NSString stringWithFormat:@"%@%@", (*nationalNumber), [fullNumber substringFromIndex:i]];
+ }
+ return potentialCountryCode;
+ }
+ }
+
+ return @0;
+}
+
+
+/**
+ * Tries to extract a country calling code from a number. This method will
+ * return zero if no country calling code is considered to be present. Country
+ * calling codes are extracted in the following ways:
+ * <ul>
+ * <li>by stripping the international dialing prefix of the region the person is
+ * dialing from, if this is present in the number, and looking at the next
+ * digits
+ * <li>by stripping the '+' sign if present and then looking at the next digits
+ * <li>by comparing the start of the number and the country calling code of the
+ * default region. If the number is not considered possible for the numbering
+ * plan of the default region initially, but starts with the country calling
+ * code of this region, validation will be reattempted after stripping this
+ * country calling code. If this number is considered a possible number, then
+ * the first digits will be considered the country calling code and removed as
+ * such.
+ * </ul>
+ *
+ * It will throw a i18n.phonenumbers.Error if the number starts with a '+' but
+ * the country calling code supplied after this does not match that of any known
+ * region.
+ *
+ * @param {string} number non-normalized telephone number that we wish to
+ * extract a country calling code from - may begin with '+'.
+ * @param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata
+ * about the region this number may be from.
+ * @param {!goog.string.StringBuffer} nationalNumber a string buffer to store
+ * the national significant number in, in the case that a country calling
+ * code was extracted. The number is appended to any existing contents. If
+ * no country calling code was extracted, this will be left unchanged.
+ * @param {boolean} keepRawInput NO if the country_code_source and
+ * preferred_carrier_code fields of phoneNumber should be populated.
+ * @param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object
+ * where the country_code and country_code_source need to be populated.
+ * Note the country_code is always populated, whereas country_code_source is
+ * only populated when keepCountryCodeSource is NO.
+ * @return {number} the country calling code extracted or 0 if none could be
+ * extracted.
+ * @throws {i18n.phonenumbers.Error}
+ */
+- (NSNumber*)maybeExtractCountryCode:(NSString*)number metadata:(NBPhoneMetaData*)defaultRegionMetadata
+ nationalNumber:(NSString**)nationalNumber keepRawInput:(BOOL)keepRawInput
+ phoneNumber:(NBPhoneNumber**)phoneNumber error:(NSError**)error
+{
+ if (nationalNumber == NULL || phoneNumber == NULL)
+ {
+ return @0;
+ }
+
+ NSNumber *res = @0;
+ @try {
+ res = [self maybeExtractCountryCode:number metadata:defaultRegionMetadata
+ nationalNumber:nationalNumber keepRawInput:keepRawInput phoneNumber:phoneNumber];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return res;
+}
+
+- (NSNumber*)maybeExtractCountryCode:(NSString*)number metadata:(NBPhoneMetaData*)defaultRegionMetadata
+ nationalNumber:(NSString**)nationalNumber keepRawInput:(BOOL)keepRawInput
+ phoneNumber:(NBPhoneNumber**)phoneNumber
+{
+ if (nationalNumber == NULL || phoneNumber == NULL || number.length <= 0)
+ {
+ return @0;
+ }
+
+ NSString *fullNumber = [number copy];
+ // Set the default prefix to be something that will never match.
+ NSString *possibleCountryIddPrefix = @"";
+ if (defaultRegionMetadata != nil)
+ {
+ possibleCountryIddPrefix = defaultRegionMetadata.internationalPrefix;
+ }
+
+ if (possibleCountryIddPrefix == nil)
+ {
+ possibleCountryIddPrefix = @"NonMatch";
+ }
+
+ /** @type {i18n.phonenumbers.PhoneNumber.CountryCodeSource} */
+ NBECountryCodeSource countryCodeSource = [self maybeStripInternationalPrefixAndNormalize:&fullNumber
+ possibleIddPrefix:possibleCountryIddPrefix];
+
+ if (keepRawInput)
+ {
+ (*phoneNumber).countryCodeSource = [NSNumber numberWithInt:countryCodeSource];
+ }
+
+ if (countryCodeSource != NBECountryCodeSourceFROM_DEFAULT_COUNTRY)
+ {
+ if (fullNumber.length <= MIN_LENGTH_FOR_NSN_)
+ {
+ @throw [NSException exceptionWithName:@"TOO_SHORT_AFTER_IDD"
+ reason:[NSString stringWithFormat:@"TOO_SHORT_AFTER_IDD:%@", fullNumber]
+ userInfo:nil];
+ }
+
+ NSNumber *potentialCountryCode = [self extractCountryCode:fullNumber nationalNumber:nationalNumber];
+
+ if (![potentialCountryCode isEqualToNumber:@0])
+ {
+ (*phoneNumber).countryCode = potentialCountryCode;
+ return potentialCountryCode;
+ }
+
+ // If this fails, they must be using a strange country calling code that we
+ // don't recognize, or that doesn't exist.
+ @throw [NSException exceptionWithName:@"INVALID_COUNTRY_CODE"
+ reason:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", potentialCountryCode]
+ userInfo:nil];
+ }
+ else if (defaultRegionMetadata != nil)
+ {
+ // Check to see if the number starts with the country calling code for the
+ // default region. If so, we remove the country calling code, and do some
+ // checks on the validity of the number before and after.
+ NSNumber *defaultCountryCode = defaultRegionMetadata.countryCode;
+ NSString *defaultCountryCodeString = [NSString stringWithFormat:@"%@", defaultCountryCode];
+ NSString *normalizedNumber = [fullNumber copy];
+
+ if ([normalizedNumber hasPrefix:defaultCountryCodeString])
+ {
+ NSString *potentialNationalNumber = [normalizedNumber substringFromIndex:defaultCountryCodeString.length];
+ NBPhoneNumberDesc *generalDesc = defaultRegionMetadata.generalDesc;
+
+ NSString *validNumberPattern = generalDesc.nationalNumberPattern;
+ // Passing null since we don't need the carrier code.
+ [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber metadata:defaultRegionMetadata carrierCode:nil];
+
+ NSString *potentialNationalNumberStr = [potentialNationalNumber copy];
+ NSString *possibleNumberPattern = generalDesc.possibleNumberPattern;
+ // If the number was not valid before but is valid now, or if it was too
+ // long before, we consider the number with the country calling code
+ // stripped to be a better result and keep that instead.
+ if ((![self matchesEntirely:validNumberPattern string:fullNumber] &&
+ [self matchesEntirely:validNumberPattern string:potentialNationalNumberStr]) ||
+ [self testNumberLengthAgainstPattern:possibleNumberPattern number:fullNumber] == NBEValidationResultTOO_LONG)
+ {
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:potentialNationalNumberStr];
+ if (keepRawInput)
+ {
+ (*phoneNumber).countryCodeSource = [NSNumber numberWithInt:NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN];
+ }
+ (*phoneNumber).countryCode = defaultCountryCode;
+ return defaultCountryCode;
+ }
+ }
+ }
+ // No country calling code present.
+ (*phoneNumber).countryCode = @0;
+ return @0;
+}
+
+
+/**
+ * Strips the IDD from the start of the number if present. Helper function used
+ * by maybeStripInternationalPrefixAndNormalize.
+ *
+ * @param {!RegExp} iddPattern the regular expression for the international
+ * prefix.
+ * @param {!goog.string.StringBuffer} number the phone number that we wish to
+ * strip any international dialing prefix from.
+ * @return {boolean} NO if an international prefix was present.
+ * @private
+ */
+- (BOOL)parsePrefixAsIdd:(NSString*)iddPattern sourceString:(NSString**)number
+{
+ if (number == NULL)
+ {
+ return NO;
+ }
+
+ NSString *numberStr = [(*number) copy];
+
+ if ([self stringPositionByRegex:numberStr regex:iddPattern] == 0)
+ {
+ NSTextCheckingResult *matched = [[self matchesByRegex:numberStr regex:iddPattern] objectAtIndex:0];
+ NSString *matchedString = [numberStr substringWithRange:matched.range];
+ NSUInteger matchEnd = matchedString.length;
+ NSString *remainString = [numberStr substringFromIndex:matchEnd];
+
+ NSRegularExpression *currentPattern = self.CAPTURING_DIGIT_PATTERN;
+ NSArray *matchedGroups = [currentPattern matchesInString:remainString options:0 range:NSMakeRange(0, remainString.length)];
+
+ if (matchedGroups && [matchedGroups count] > 0 && [matchedGroups objectAtIndex:0] != nil)
+ {
+ NSString *digitMatched = [remainString substringWithRange:((NSTextCheckingResult*)[matchedGroups objectAtIndex:0]).range];
+ if (digitMatched.length > 0)
+ {
+ NSString *normalizedGroup = [self normalizeDigitsOnly:digitMatched];
+ if ([normalizedGroup isEqualToString:@"0"]) {
+ return NO;
+ }
+ }
+ }
+
+ (*number) = [remainString copy];
+ return YES;
+ }
+
+ return NO;
+}
+
+
+/**
+ * Strips any international prefix (such as +, 00, 011) present in the number
+ * provided, normalizes the resulting number, and indicates if an international
+ * prefix was present.
+ *
+ * @param {!goog.string.StringBuffer} number the non-normalized telephone number
+ * that we wish to strip any international dialing prefix from.
+ * @param {string} possibleIddPrefix the international direct dialing prefix
+ * from the region we think this number may be dialed in.
+ * @return {CountryCodeSource} the corresponding
+ * CountryCodeSource if an international dialing prefix could be removed
+ * from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if
+ * the number did not seem to be in international format.
+ */
+- (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString**)numberStr possibleIddPrefix:(NSString*)possibleIddPrefix error:(NSError**)error
+{
+ if (numberStr == NULL)
+ {
+ return 0;
+ }
+
+ NBECountryCodeSource res = 0;
+ @try {
+ res = [self maybeStripInternationalPrefixAndNormalize:numberStr possibleIddPrefix:possibleIddPrefix];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString**)numberStr possibleIddPrefix:(NSString*)possibleIddPrefix
+{
+ if (numberStr == NULL || (*numberStr).length == 0)
+ {
+ return NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
+ }
+
+ // Check to see if the number begins with one or more plus signs.
+ if ([self isStartingStringByRegex:(*numberStr) regex:self.LEADING_PLUS_CHARS_PATTERN_])
+ {
+ (*numberStr) = [self replaceStringByRegex:(*numberStr) regex:self.LEADING_PLUS_CHARS_PATTERN_ withTemplate:@""];
+ // Can now normalize the rest of the number since we've consumed the '+'
+ // sign at the start.
+ (*numberStr) = [self normalizePhoneNumber:(*numberStr)];
+ return NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN;
+ }
+
+ // Attempt to parse the first digits as an international prefix.
+ NSString *iddPattern = [possibleIddPrefix copy];
+ [self normalizeSB:numberStr];
+
+ return [self parsePrefixAsIdd:iddPattern sourceString:numberStr] ? NBECountryCodeSourceFROM_NUMBER_WITH_IDD : NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
+}
+
+
+/**
+ * Strips any national prefix (such as 0, 1) present in the number provided.
+ *
+ * @param {!goog.string.StringBuffer} number the normalized telephone number
+ * that we wish to strip any national dialing prefix from.
+ * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
+ * region that we think this number is from.
+ * @param {goog.string.StringBuffer} carrierCode a place to insert the carrier
+ * code if one is extracted.
+ * @return {boolean} NO if a national prefix or carrier code (or both) could
+ * be extracted.
+ */
+- (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString**)number metadata:(NBPhoneMetaData*)metadata carrierCode:(NSString**)carrierCode error:(NSError**)error
+{
+ if (number == NULL)
+ {
+ return NO;
+ }
+
+ BOOL res = NO;
+ @try {
+ res = [self maybeStripNationalPrefixAndCarrierCode:number metadata:metadata carrierCode:carrierCode];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+
+- (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString**)number metadata:(NBPhoneMetaData*)metadata carrierCode:(NSString**)carrierCode
+{
+ if (number == NULL)
+ {
+ return NO;
+ }
+
+ NSString *numberStr = [(*number) copy];
+ NSUInteger numberLength = numberStr.length;
+ NSString *possibleNationalPrefix = metadata.nationalPrefixForParsing;
+
+ if (numberLength == 0 || [self hasValue:possibleNationalPrefix] == NO)
+ {
+ // Early return for numbers of zero length.
+ return NO;
+ }
+
+ // Attempt to parse the first digits as a national prefix.
+ NSString *prefixPattern = [NSString stringWithFormat:@"^(?:%@)", possibleNationalPrefix];
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:prefixPattern
+ options:0 error:&error];
+
+ NSArray *prefixMatcher = [currentPattern matchesInString:numberStr options:0 range:NSMakeRange(0, numberLength)];
+ if (prefixMatcher && [prefixMatcher count] > 0)
+ {
+ NSString *nationalNumberRule = metadata.generalDesc.nationalNumberPattern;
+ NSTextCheckingResult *firstMatch = [prefixMatcher objectAtIndex:0];
+ NSString *firstMatchString = [numberStr substringWithRange:firstMatch.range];
+
+ // prefixMatcher[numOfGroups] == null implies nothing was captured by the
+ // capturing groups in possibleNationalPrefix; therefore, no transformation
+ // is necessary, and we just remove the national prefix.
+ NSUInteger numOfGroups = firstMatch.numberOfRanges - 1;
+ NSString *transformRule = metadata.nationalPrefixTransformRule;
+ NSString *transformedNumber = @"";
+ NSRange firstRange = [firstMatch rangeAtIndex:numOfGroups];
+ NSString *firstMatchStringWithGroup = (firstRange.location != NSNotFound && firstRange.location < numberStr.length) ? [numberStr substringWithRange:firstRange] : nil;
+ BOOL noTransform = (transformRule == nil || transformRule.length == 0 || [self hasValue:firstMatchStringWithGroup] == NO);
+
+ if (noTransform)
+ {
+ transformedNumber = [numberStr substringFromIndex:firstMatchString.length];
+ }
+ else
+ {
+ transformedNumber = [self replaceFirstStringByRegex:numberStr regex:prefixPattern withTemplate:transformRule];
+ }
+ // If the original number was viable, and the resultant number is not,
+ // we return.
+ if ([self hasValue:nationalNumberRule ] && [self matchesEntirely:nationalNumberRule string:numberStr] &&
+ [self matchesEntirely:nationalNumberRule string:transformedNumber] == NO)
+ {
+ return NO;
+ }
+
+ if ((noTransform && numOfGroups > 0 && [self hasValue:firstMatchStringWithGroup]) || (!noTransform && numOfGroups > 1))
+ {
+ if (carrierCode != NULL && (*carrierCode) != nil)
+ {
+ (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchStringWithGroup];
+ }
+ }
+ else if ((noTransform && numOfGroups > 0 && [self hasValue:firstMatchString]) || (!noTransform && numOfGroups > 1))
+ {
+ if (carrierCode != NULL && (*carrierCode) != nil)
+ {
+ (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchString];
+ }
+ }
+
+ (*number) = transformedNumber;
+ return YES;
+ }
+ return NO;
+}
+
+
+/**
+ * Strips any extension (as in, the part of the number dialled after the call is
+ * connected, usually indicated with extn, ext, x or similar) from the end of
+ * the number, and returns it.
+ *
+ * @param {!goog.string.StringBuffer} number the non-normalized telephone number
+ * that we wish to strip the extension from.
+ * @return {string} the phone extension.
+ */
+- (NSString*)maybeStripExtension:(NSString**)number
+{
+ if (number == NULL)
+ {
+ return @"";
+ }
+
+ NSString *numberStr = [(*number) copy];
+ NSInteger mStart = [self stringPositionByRegex:numberStr regex:self.EXTN_PATTERN_];
+
+ // If we find a potential extension, and the number preceding this is a viable
+ // number, we assume it is an extension.
+ if (mStart >= 0 && [self isViablePhoneNumber:[numberStr substringWithRange:NSMakeRange(0, mStart)]])
+ {
+ // The numbers are captured into groups in the regular expression.
+ NSTextCheckingResult *firstMatch = [self matcheFirstByRegex:numberStr regex:self.EXTN_PATTERN_];
+ NSUInteger matchedGroupsLength = [firstMatch numberOfRanges];
+ for (NSUInteger i=1; i<matchedGroupsLength; i++)
+ {
+ NSRange curRange = [firstMatch rangeAtIndex:i];
+ if (curRange.location != NSNotFound && curRange.location < numberStr.length)
+ {
+ NSString *matchString = [(*number) substringWithRange:curRange];
+ // We go through the capturing groups until we find one that captured
+ // some digits. If none did, then we will return the empty string.
+ NSString *tokenedString = [numberStr substringWithRange:NSMakeRange(0, mStart)];
+ (*number) = @"";
+ (*number) = [(*number) stringByAppendingString:tokenedString];
+
+ return matchString;
+ }
+ }
+ }
+
+ return @"";
+}
+
+
+/**
+ * Checks to see that the region code used is valid, or if it is not valid, that
+ * the number to parse starts with a + symbol so that we can attempt to infer
+ * the region from the number.
+ * @param {string} numberToParse number that we are attempting to parse.
+ * @param {?string} defaultRegion region that we are expecting the number to be
+ * from.
+ * @return {boolean} NO if it cannot use the region provided and the region
+ * cannot be inferred.
+ * @private
+ */
+- (BOOL)checkRegionForParsing:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion
+{
+ // If the number is nil or empty, we can't infer the region.
+ return [self isValidRegionCode:defaultRegion] ||
+ (numberToParse != nil && numberToParse.length > 0 && [self isStartingStringByRegex:numberToParse regex:self.LEADING_PLUS_CHARS_PATTERN_]);
+}
+
+
+/**
+ * Parses a string and returns it in proto buffer format. This method will throw
+ * a {@link i18n.phonenumbers.Error} if the number is not considered to be a
+ * possible number. Note that validation of whether the number is actually a
+ * valid number for a particular region is not performed. This can be done
+ * separately with {@link #isValidNumber}.
+ *
+ * @param {?string} numberToParse number that we are attempting to parse. This
+ * can contain formatting such as +, ( and -, as well as a phone number
+ * extension. It can also be provided in RFC3966 format.
+ * @param {?string} defaultRegion region that we are expecting the number to be
+ * from. This is only used if the number being parsed is not written in
+ * international format. The country_code for the number in this case would
+ * be stored as that of the default region supplied. If the number is
+ * guaranteed to start with a '+' followed by the country calling code, then
+ * 'ZZ' or nil can be supplied.
+ * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
+ * with the parsed number.
+ * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
+ * viable phone number or if no default region was supplied and the number
+ * is not in international format (does not start with +).
+ */
+- (NBPhoneNumber*)parse:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion
+{
+ return [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:NO checkRegion:YES];
+}
+
+
+- (NBPhoneNumber*)parse:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error
+{
+ NBPhoneNumber *phoneNumber = nil;
+
+ @try {
+ phoneNumber = [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:NO checkRegion:YES];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+
+ return phoneNumber;
+}
+
+/**
+ * Parses a string using the phone's carrier region (when available, ZZ otherwise).
+ * This uses the country the sim card in the phone is registered with.
+ * For example if you have an AT&T sim card but are in Europe, this will parse the
+ * number using +1 (AT&T is a US Carrier) as the default country code.
+ * This also works for CDMA phones which don't have a sim card.
+ */
+- (NBPhoneNumber*)parseWithPhoneCarrierRegion:(NSString*)numberToParse error:(NSError**)error
+{
+ NSString *(^ISOCountryCodeByCarrier)() = ^() {
+ CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init];
+ CTCarrier *carrier = [networkInfo subscriberCellularProvider];
+ return [carrier isoCountryCode];
+ };
+ NSString *isoCode = ISOCountryCodeByCarrier();
+
+ if (!isoCode) {
+ isoCode = @"ZZ";
+ }
+
+ return [self parse:numberToParse defaultRegion:isoCode];
+}
+
+
+/**
+ * Parses a string and returns it in proto buffer format. This method differs
+ * from {@link #parse} in that it always populates the raw_input field of the
+ * protocol buffer with numberToParse as well as the country_code_source field.
+ *
+ * @param {string} numberToParse number that we are attempting to parse. This
+ * can contain formatting such as +, ( and -, as well as a phone number
+ * extension.
+ * @param {?string} defaultRegion region that we are expecting the number to be
+ * from. This is only used if the number being parsed is not written in
+ * international format. The country calling code for the number in this
+ * case would be stored as that of the default region supplied.
+ * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
+ * with the parsed number.
+ * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
+ * viable phone number or if no default region was supplied.
+ */
+- (NBPhoneNumber*)parseAndKeepRawInput:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error
+{
+ NBPhoneNumber *res = nil;
+ @try {
+ res = [self parseAndKeepRawInput:numberToParse defaultRegion:defaultRegion];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NBPhoneNumber*)parseAndKeepRawInput:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion
+{
+ if ([self isValidRegionCode:defaultRegion] == NO)
+ {
+ if (numberToParse.length > 0 && [numberToParse hasPrefix:@"+"] == NO)
+ {
+ NSException* metaException = [NSException exceptionWithName:@"INVALID_COUNTRY_CODE"
+ reason:[NSString stringWithFormat:@"Invalid country code:%@", numberToParse]
+ userInfo:nil];
+ @throw metaException;
+ }
+ }
+ return [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:YES checkRegion:YES];
+}
+
+
+/**
+ * Parses a string and returns it in proto buffer format. This method is the
+ * same as the public {@link #parse} method, with the exception that it allows
+ * the default region to be nil, for use by {@link #isNumberMatch}.
+ *
+ * @param {?string} numberToParse number that we are attempting to parse. This
+ * can contain formatting such as +, ( and -, as well as a phone number
+ * extension.
+ * @param {?string} defaultRegion region that we are expecting the number to be
+ * from. This is only used if the number being parsed is not written in
+ * international format. The country calling code for the number in this
+ * case would be stored as that of the default region supplied.
+ * @param {boolean} keepRawInput whether to populate the raw_input field of the
+ * phoneNumber with numberToParse.
+ * @param {boolean} checkRegion should be set to NO if it is permitted for
+ * the default coregion to be nil or unknown ('ZZ').
+ * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
+ * with the parsed number.
+ * @throws {i18n.phonenumbers.Error}
+ * @private
+ */
+- (NBPhoneNumber*)parseHelper:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion
+ keepRawInput:(BOOL)keepRawInput checkRegion:(BOOL)checkRegion error:(NSError**)error
+{
+ NBPhoneNumber *res = nil;
+ @try {
+ res = [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:keepRawInput checkRegion:checkRegion];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NBPhoneNumber*)parseHelper:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion
+ keepRawInput:(BOOL)keepRawInput checkRegion:(BOOL)checkRegion
+{
+ if (numberToParse == nil)
+ {
+ @throw [NSException exceptionWithName:@"NOT_A_NUMBER" reason:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", numberToParse] userInfo:nil];
+ }
+ else if (numberToParse.length > MAX_INPUT_STRING_LENGTH_)
+ {
+ @throw [NSException exceptionWithName:@"TOO_LONG" reason:[NSString stringWithFormat:@"TOO_LONG:%@", numberToParse] userInfo:nil];
+ }
+
+ NSString *nationalNumber = @"";
+ [self buildNationalNumberForParsing:numberToParse nationalNumber:&nationalNumber];
+
+ if ([self isViablePhoneNumber:nationalNumber] == NO)
+ {
+ @throw [NSException exceptionWithName:@"NOT_A_NUMBER" reason:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", nationalNumber] userInfo:nil];
+ }
+
+ // Check the region supplied is valid, or that the extracted number starts
+ // with some sort of + sign so the number's region can be determined.
+ if (checkRegion && [self checkRegionForParsing:nationalNumber defaultRegion:defaultRegion] == NO)
+ {
+ @throw [NSException exceptionWithName:@"INVALID_COUNTRY_CODE" reason:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", defaultRegion] userInfo:nil];
+ }
+
+ NBPhoneNumber *phoneNumber = [[NBPhoneNumber alloc] init];
+ if (keepRawInput)
+ {
+ phoneNumber.rawInput = [numberToParse copy];
+ }
+ // Attempt to parse extension first, since it doesn't require region-specific
+ // data and we want to have the non-normalised number here.
+ NSString *extension = [self maybeStripExtension:&nationalNumber];
+ if (extension.length > 0)
+ {
+ phoneNumber.extension = [extension copy];
+ }
+
+ NBPhoneMetaData *regionMetadata = [self getMetadataForRegion:defaultRegion];
+ // Check to see if the number is given in international format so we know
+ // whether this number is from the default region or not.
+ NSString *normalizedNationalNumber = @"";
+ NSNumber *countryCode = @0;
+ NSString *nationalNumberStr = [nationalNumber copy];
+ @try {
+ countryCode = [self maybeExtractCountryCode:nationalNumberStr
+ metadata:regionMetadata
+ nationalNumber:&normalizedNationalNumber
+ keepRawInput:keepRawInput
+ phoneNumber:&phoneNumber];
+ }
+ @catch (NSException *e) {
+ if ([e.name isEqualToString:@"INVALID_COUNTRY_CODE"] && [self stringPositionByRegex:nationalNumberStr
+ regex:self.LEADING_PLUS_CHARS_PATTERN_] >= 0)
+ {
+ // Strip the plus-char, and try again.
+ nationalNumberStr = [self replaceStringByRegex:nationalNumberStr regex:self.LEADING_PLUS_CHARS_PATTERN_ withTemplate:@""];
+ countryCode = [self maybeExtractCountryCode:nationalNumberStr
+ metadata:regionMetadata
+ nationalNumber:&normalizedNationalNumber
+ keepRawInput:keepRawInput
+ phoneNumber:&phoneNumber];
+ if ([countryCode isEqualToNumber:@0])
+ {
+ @throw [NSException exceptionWithName:e.name
+ reason:[NSString stringWithFormat:@"%@:%@", e.name, nationalNumberStr]
+ userInfo:nil];
+ }
+ }
+ else
+ {
+ @throw e;
+ }
+ }
+
+ if (![countryCode isEqualToNumber:@0])
+ {
+ NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCode];
+ if (phoneNumberRegion != defaultRegion)
+ {
+ // Metadata cannot be nil because the country calling code is valid.
+ regionMetadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:phoneNumberRegion];
+ }
+ }
+ else
+ {
+ // If no extracted country calling code, use the region supplied instead.
+ // The national number is just the normalized version of the number we were
+ // given to parse.
+ [self normalizeSB:&nationalNumber];
+ normalizedNationalNumber = [normalizedNationalNumber stringByAppendingString:nationalNumber];
+
+ if (defaultRegion != nil)
+ {
+ countryCode = regionMetadata.countryCode;
+ phoneNumber.countryCode = countryCode;
+ }
+ else if (keepRawInput)
+ {
+ [phoneNumber clearCountryCodeSource];
+ }
+ }
+
+ if (normalizedNationalNumber.length < MIN_LENGTH_FOR_NSN_)
+ {
+ @throw [NSException exceptionWithName:@"TOO_SHORT_NSN"
+ reason:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
+ userInfo:nil];
+ }
+
+ if (regionMetadata != nil)
+ {
+ NSString *carrierCode = @"";
+ [self maybeStripNationalPrefixAndCarrierCode:&normalizedNationalNumber metadata:regionMetadata carrierCode:&carrierCode];
+
+ if (keepRawInput)
+ {
+ phoneNumber.PreferredDomesticCarrierCode = [carrierCode copy];
+ }
+ }
+
+ NSString *normalizedNationalNumberStr = [normalizedNationalNumber copy];
+
+ NSUInteger lengthOfNationalNumber = normalizedNationalNumberStr.length;
+ if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_)
+ {
+ @throw [NSException exceptionWithName:@"TOO_SHORT_NSN"
+ reason:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumberStr]
+ userInfo:nil];
+ }
+
+ if (lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_)
+ {
+ @throw [NSException exceptionWithName:@"TOO_LONG"
+ reason:[NSString stringWithFormat:@"TOO_LONG:%@", normalizedNationalNumberStr]
+ userInfo:nil];
+ }
+
+ if ([normalizedNationalNumberStr hasPrefix:@"0"])
+ {
+ phoneNumber.italianLeadingZero = YES;
+ }
+
+ phoneNumber.nationalNumber = [NSNumber numberWithLongLong:(UInt64)[normalizedNationalNumberStr longLongValue]];
+ return phoneNumber;
+}
+
+
+/**
+ * Converts numberToParse to a form that we can parse and write it to
+ * nationalNumber if it is written in RFC3966; otherwise extract a possible
+ * number out of it and write to nationalNumber.
+ *
+ * @param {?string} numberToParse number that we are attempting to parse. This
+ * can contain formatting such as +, ( and -, as well as a phone number
+ * extension.
+ * @param {!goog.string.StringBuffer} nationalNumber a string buffer for storing
+ * the national significant number.
+ * @private
+ */
+- (void)buildNationalNumberForParsing:(NSString*)numberToParse nationalNumber:(NSString**)nationalNumber
+{
+ if (nationalNumber == NULL)
+ return;
+
+ NSInteger indexOfPhoneContext = [self indexOfStringByString:numberToParse target:RFC3966_PHONE_CONTEXT_];
+ if (indexOfPhoneContext > 0)
+ {
+ NSUInteger phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT_.length;
+ // If the phone context contains a phone number prefix, we need to capture
+ // it, whereas domains will be ignored.
+ if ([numberToParse characterAtIndex:phoneContextStart] == '+')
+ {
+ // Additional parameters might follow the phone context. If so, we will
+ // remove them here because the parameters after phone context are not
+ // important for parsing the phone number.
+ NSRange foundRange = [numberToParse rangeOfString:@";" options:NSLiteralSearch range:NSMakeRange(phoneContextStart, numberToParse.length - phoneContextStart)];
+ if (foundRange.location != NSNotFound)
+ {
+ NSRange subRange = NSMakeRange(phoneContextStart, foundRange.location - phoneContextStart);
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[numberToParse substringWithRange:subRange]];
+ }
+ else
+ {
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[numberToParse substringFromIndex:phoneContextStart]];
+ }
+ }
+
+ // Now append everything between the "tel:" prefix and the phone-context.
+ // This should include the national number, an optional extension or
+ // isdn-subaddress component.
+ NSUInteger rfc3966Start = [self indexOfStringByString:numberToParse target:RFC3966_PREFIX_] + RFC3966_PREFIX_.length;
+ NSString *subString = [numberToParse substringWithRange:NSMakeRange(rfc3966Start, indexOfPhoneContext - rfc3966Start)];
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:subString];
+ }
+ else
+ {
+ // Extract a possible number from the string passed in (this strips leading
+ // characters that could not be the start of a phone number.)
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[self extractPossibleNumber:numberToParse]];
+ }
+
+ // Delete the isdn-subaddress and everything after it if it is present.
+ // Note extension won't appear at the same time with isdn-subaddress
+ // according to paragraph 5.3 of the RFC3966 spec,
+ NSString *nationalNumberStr = [(*nationalNumber) copy];
+ NSInteger indexOfIsdn = [self indexOfStringByString:nationalNumberStr target:RFC3966_ISDN_SUBADDRESS_];
+ if (indexOfIsdn > 0)
+ {
+ (*nationalNumber) = @"";
+ (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[nationalNumberStr substringWithRange:NSMakeRange(0, indexOfIsdn)]];
+ }
+ // If both phone context and isdn-subaddress are absent but other
+ // parameters are present, the parameters are left in nationalNumber. This
+ // is because we are concerned about deleting content from a potential
+ // number string when there is no strong evidence that the number is
+ // actually written in RFC3966.
+}
+
+
+/**
+ * Takes two phone numbers and compares them for equality.
+ *
+ * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
+ * for Italian numbers and any extension present are the same. Returns NSN_MATCH
+ * if either or both has no region specified, and the NSNs and extensions are
+ * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
+ * or the region specified is the same, and one NSN could be a shorter version
+ * of the other number. This includes the case where one has an extension
+ * specified, and the other does not. Returns NO_MATCH otherwise. For example,
+ * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
+ * +1 345 657 1234 and 345 657 are a NO_MATCH.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber|string} firstNumberIn first number to
+ * compare. If it is a string it can contain formatting, and can have
+ * country calling code specified with + at the start.
+ * @param {i18n.phonenumbers.PhoneNumber|string} secondNumberIn second number to
+ * compare. If it is a string it can contain formatting, and can have
+ * country calling code specified with + at the start.
+ * @return {MatchType} NOT_A_NUMBER, NO_MATCH,
+ * SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of
+ * equality of the two numbers, described in the method definition.
+ */
+- (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn error:(NSError**)error
+{
+ NBEMatchType res = 0;
+ @try {
+ res = [self isNumberMatch:firstNumberIn second:secondNumberIn];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn
+{
+
+ // If the input arguements are strings parse them to a proto buffer format.
+ // Else make copies of the phone numbers so that the numbers passed in are not
+ // edited.
+ /** @type {i18n.phonenumbers.PhoneNumber} */
+ NBPhoneNumber *firstNumber = nil, *secondNumber = nil;
+ if ([firstNumberIn isKindOfClass:[NSString class]])
+ {
+ // First see if the first number has an implicit country calling code, by
+ // attempting to parse it.
+ @try {
+ firstNumber = [self parse:firstNumberIn defaultRegion:UNKNOWN_REGION_];
+ }
+ @catch (NSException *e) {
+ if ([e.name isEqualToString:@"INVALID_COUNTRY_CODE"] == NO)
+ {
+ return NBEMatchTypeNOT_A_NUMBER;
+ }
+ // The first number has no country calling code. EXACT_MATCH is no longer
+ // possible. We parse it as if the region was the same as that for the
+ // second number, and if EXACT_MATCH is returned, we replace this with
+ // NSN_MATCH.
+ if ([secondNumberIn isKindOfClass:[NSString class]] == NO)
+ {
+ NSString *secondNumberRegion = [self getRegionCodeForCountryCode:((NBPhoneNumber*)secondNumberIn).countryCode];
+ if (secondNumberRegion != UNKNOWN_REGION_)
+ {
+ @try {
+ firstNumber = [self parse:firstNumberIn defaultRegion:secondNumberRegion];
+ }
+ @catch (NSException *e2) {
+ return NBEMatchTypeNOT_A_NUMBER;
+ }
+
+ NBEMatchType match = [self isNumberMatch:firstNumber second:secondNumberIn];
+ if (match == NBEMatchTypeEXACT_MATCH)
+ {
+ return NBEMatchTypeNSN_MATCH;
+ }
+ return match;
+ }
+ }
+ // If the second number is a string or doesn't have a valid country
+ // calling code, we parse the first number without country calling code.
+ @try {
+ firstNumber = [self parseHelper:firstNumberIn defaultRegion:nil keepRawInput:NO checkRegion:NO];
+ }
+ @catch (NSException *e2) {
+ return NBEMatchTypeNOT_A_NUMBER;
+ }
+ }
+ }
+ else
+ {
+ firstNumber = [firstNumberIn copy];
+ }
+
+ if ([secondNumberIn isKindOfClass:[NSString class]])
+ {
+ @try {
+ secondNumber = [self parse:secondNumberIn defaultRegion:UNKNOWN_REGION_];
+ return [self isNumberMatch:firstNumberIn second:secondNumber];
+ }
+ @catch (NSException *e2) {
+ if ([e2.name isEqualToString:@"INVALID_COUNTRY_CODE"] == NO)
+ {
+ return NBEMatchTypeNOT_A_NUMBER;
+ }
+ return [self isNumberMatch:secondNumberIn second:firstNumber];
+ }
+ }
+ else
+ {
+ secondNumber = [secondNumberIn copy];
+ }
+
+ // First clear raw_input, country_code_source and
+ // preferred_domestic_carrier_code fields and any empty-string extensions so
+ // that we can use the proto-buffer equality method.
+ firstNumber.rawInput = @"";
+ [firstNumber clearCountryCodeSource];
+ firstNumber.PreferredDomesticCarrierCode = @"";
+
+ secondNumber.rawInput = @"";
+ [secondNumber clearCountryCodeSource];
+ secondNumber.PreferredDomesticCarrierCode = @"";
+
+ if (firstNumber.extension != nil && firstNumber.extension.length == 0)
+ {
+ firstNumber.extension = nil;
+ }
+
+ if (secondNumber.extension != nil && secondNumber.extension.length == 0)
+ {
+ secondNumber.extension = nil;
+ }
+
+ // Early exit if both had extensions and these are different.
+ if ([self hasValue:firstNumber.extension] && [self hasValue:secondNumber.extension] &&
+ [firstNumber.extension isEqualToString:secondNumber.extension] == NO)
+ {
+ return NBEMatchTypeNO_MATCH;
+ }
+
+ NSNumber *firstNumberCountryCode = firstNumber.countryCode;
+ NSNumber *secondNumberCountryCode = secondNumber.countryCode;
+
+ // Both had country_code specified.
+ if (![firstNumberCountryCode isEqualToNumber:@0] && ![secondNumberCountryCode isEqualToNumber:@0])
+ {
+ if ([firstNumber isEqual:secondNumber])
+ {
+ return NBEMatchTypeEXACT_MATCH;
+ }
+ else if ([firstNumberCountryCode isEqualToNumber:secondNumberCountryCode] && [self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber])
+ {
+ // A SHORT_NSN_MATCH occurs if there is a difference because of the
+ // presence or absence of an 'Italian leading zero', the presence or
+ // absence of an extension, or one NSN being a shorter variant of the
+ // other.
+ return NBEMatchTypeSHORT_NSN_MATCH;
+ }
+ // This is not a match.
+ return NBEMatchTypeNO_MATCH;
+ }
+ // Checks cases where one or both country_code fields were not specified. To
+ // make equality checks easier, we first set the country_code fields to be
+ // equal.
+ firstNumber.countryCode = @0;
+ secondNumber.countryCode = @0;
+ // If all else was the same, then this is an NSN_MATCH.
+ if ([firstNumber isEqual:secondNumber])
+ {
+ return NBEMatchTypeNSN_MATCH;
+ }
+
+ if ([self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber])
+ {
+ return NBEMatchTypeSHORT_NSN_MATCH;
+ }
+ return NBEMatchTypeNO_MATCH;
+}
+
+
+/**
+ * Returns NO when one national number is the suffix of the other or both are
+ * the same.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} firstNumber the first PhoneNumber
+ * object.
+ * @param {i18n.phonenumbers.PhoneNumber} secondNumber the second PhoneNumber
+ * object.
+ * @return {boolean} NO if one PhoneNumber is the suffix of the other one.
+ * @private
+ */
+- (BOOL)isNationalNumberSuffixOfTheOther:(NBPhoneNumber*)firstNumber second:(NBPhoneNumber*)secondNumber
+{
+ NSString *firstNumberNationalNumber = [NSString stringWithFormat:@"%@", firstNumber.nationalNumber];
+ NSString *secondNumberNationalNumber = [NSString stringWithFormat:@"%@", secondNumber.nationalNumber];
+
+ // Note that endsWith returns NO if the numbers are equal.
+ return [firstNumberNationalNumber hasSuffix:secondNumberNationalNumber] ||
+ [secondNumberNationalNumber hasSuffix:firstNumberNationalNumber];
+}
+
+
+/**
+ * Returns NO if the number can be dialled from outside the region, or
+ * unknown. If the number can only be dialled from within the region, returns
+ * NO. Does not check the number is a valid number.
+ * TODO: Make this method public when we have enough metadata to make it
+ * worthwhile. Currently visible for testing purposes only.
+ *
+ * @param {i18n.phonenumbers.PhoneNumber} number the phone-number for which we
+ * want to know whether it is diallable from outside the region.
+ * @return {boolean} NO if the number can only be dialled from within the
+ * country.
+ */
+- (BOOL)canBeInternationallyDialled:(NBPhoneNumber*)number error:(NSError**)error
+{
+ BOOL res = NO;
+ @try {
+ res = [self canBeInternationallyDialled:number];
+ }
+ @catch (NSException *exception) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason
+ forKey:NSLocalizedDescriptionKey];
+ if (error != NULL)
+ (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
+ }
+ return res;
+}
+
+- (BOOL)canBeInternationallyDialled:(NBPhoneNumber*)number
+{
+ NBPhoneMetaData *metadata = [self getMetadataForRegion:[self getRegionCodeForNumber:number]];
+ if (metadata == nil)
+ {
+ // Note numbers belonging to non-geographical entities (e.g. +800 numbers)
+ // are always internationally diallable, and will be caught here.
+ return YES;
+ }
+ NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
+ return [self isNumberMatchingDesc:nationalSignificantNumber numberDesc:metadata.noInternationalDialling] == NO;
+}
+
+
+/**
+ * Check whether the entire input sequence can be matched against the regular
+ * expression.
+ *
+ * @param {!RegExp|string} regex the regular expression to match against.
+ * @param {string} str the string to test.
+ * @return {boolean} NO if str can be matched entirely against regex.
+ * @private
+ */
+- (BOOL)matchesEntirely:(NSString*)regex string:(NSString*)str
+{
+ if ([regex rangeOfString:@"^"].location == NSNotFound)
+ {
+ regex = [NSString stringWithFormat:@"^(?:%@)$", regex];
+ }
+
+ NSError *error = nil;
+ NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:&error];
+ NSTextCheckingResult *matchResult = [currentPattern firstMatchInString:str options:0 range:NSMakeRange(0, str.length)];
+
+ if (matchResult != nil)
+ {
+ NSString *founds = [str substringWithRange:matchResult.range];
+
+ if ([founds isEqualToString:str])
+ {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+
+@end