1 /*
  2  * PhoneNumber.js - Represent a phone number.
  3  *
  4  * Copyright © 2014-2015, 2018, JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 // !data states idd mnc
 21 
 22 var ilib = require("../index.js");
 23 var Utils = require("./Utils.js");
 24 var JSUtils = require("./JSUtils.js");
 25 var NumberingPlan = require("./NumberingPlan.js");
 26 var PhoneLocale = require("./PhoneLocale.js");
 27 var PhoneHandlerFactory = require("./PhoneHandlerFactory.js");
 28 
 29 /**
 30  * @class
 31  * Create a new phone number instance that parses the phone number parameter for its
 32  * constituent parts, and store them as separate fields in the returned object.
 33  *
 34  * The options object may include any of these properties:
 35  *
 36  * <ul>
 37  * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which
 38  * numbering plan to use.
 39  * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is
 40  * currently connected to, if known. This also can give a clue as to which numbering plan to
 41  * use
 42  * <li>onLoad - a callback function to call when this instance is fully
 43  * loaded. When the onLoad option is given, this class will attempt to
 44  * load any missing locale data using the ilib loader callback.
 45  * When the constructor is done (even if the data is already preassembled), the
 46  * onLoad function is called with the current instance as a parameter, so this
 47  * callback can be used with preassembled or dynamic loading or a mix of the two.
 48  * <li>sync - tell whether to load any missing locale data synchronously or
 49  * asynchronously. If this option is given as "false", then the "onLoad"
 50  * callback must be given, as the instance returned from this constructor will
 51  * not be usable for a while.
 52  * <li><i>loadParams</i> - an object containing parameters to pass to the
 53  * loader callback function when locale data is missing. The parameters are not
 54  * interpretted or modified in any way. They are simply passed along. The object
 55  * may contain any property/value pairs as long as the calling code is in
 56  * agreement with the loader callback function as to what those parameters mean.
 57  * </ul>
 58  *
 59  * This function is locale-sensitive, and will assume any number passed to it is
 60  * appropriate for the given locale. If the MCC is given, this method will assume
 61  * that numbers without an explicit country code have been dialled within the country
 62  * given by the MCC. This affects how things like area codes are parsed. If the MCC
 63  * is not given, this method will use the given locale to determine the country
 64  * code. If the locale is not explicitly given either, then this function uses the
 65  * region of current locale as the default.<p>
 66  *
 67  * The input number may contain any formatting characters for the given locale. Each
 68  * field that is returned in the json object is a simple string of digits with
 69  * all formatting and whitespace characters removed.<p>
 70  *
 71  * The number is decomposed into its parts, regardless if the number
 72  * contains formatting characters. If a particular part cannot be extracted from given
 73  * number, the field will not be returned as a field in the object. If no fields can be
 74  * extracted from the number at all, then all digits found in the string will be
 75  * returned in the subscriberNumber field. If the number parameter contains no
 76  * digits, an empty object is returned.<p>
 77  *
 78  * This instance can contain any of the following fields after parsing is done:
 79  *
 80  * <ul>
 81  * <li>vsc - if this number starts with a VSC (Vertical Service Code, or "star code"), this field will contain the star and the code together
 82  * <li>iddPrefix - the prefix for international direct dialing. This can either be in the form of a plus character or the IDD access code for the given locale
 83  * <li>countryCode - if this number is an international direct dial number, this is the country code
 84  * <li>cic - for "dial-around" services (access to other carriers), this is the prefix used as the carrier identification code
 85  * <li>emergency - an emergency services number
 86  * <li>mobilePrefix - prefix that introduces a mobile phone number
 87  * <li>trunkAccess - trunk access code (long-distance access)
 88  * <li>serviceCode - like a geographic area code, but it is a required prefix for various services
 89  * <li>areaCode - geographic area codes
 90  * <li>subscriberNumber - the unique number of the person or company that pays for this phone line
 91  * <li>extension - in some countries, extensions are dialed directly without going through an operator or a voice prompt system. If the number includes an extension, it is given in this field.
 92  * <li>invalid - this property is added and set to true if the parser found that the number is invalid in the numbering plan for the country. This method will make its best effort at parsing, but any digits after the error will go into the subscriberNumber field
 93  * </ul>
 94  *
 95  * The following rules determine how the number is parsed:
 96  *
 97  * <ol>
 98  * <li>If the number starts with a character that is alphabetic instead of numeric, do
 99  * not parse the number at all. There is a good chance that it is not really a phone number.
100  * In this case, an empty instance will be returned.
101  * <li>If the phone number uses the plus notation or explicitly uses the international direct
102  * dialing prefix for the given locale, then the country code is identified in
103  * the number. The rules of given locale are used to parse the IDD prefix, and then the rules
104  * of the country in the prefix are used to parse the rest of the number.
105  * <li>If a country code is provided as an argument to the function call, use that country's
106  * parsing rules for the number. This is intended for programs like a Contacts application that
107  * know what the country is of the person that owns the phone number and can pass that on as
108  * a hint.
109  * <li>If the appropriate locale cannot be easily determined, default to using the rules
110  * for the current user's region.
111  * </ol>
112  *
113  * Example: parsing the number "+49 02101345345-78" will give the following properties in the
114  * resulting phone number instance:
115  *
116  * <pre>
117  *      {
118  *        iddPrefix: "+",
119  *        countryCode: "49",
120  *        areaCode: "02101",
121  *        subscriberNumber: "345345",
122  *        extension: "78"
123  *      }
124  * </pre>
125  *
126  * Note that in this example, because international direct dialing is explicitly used
127  * in the number, the part of this number after the IDD prefix and country code will be
128  * parsed exactly the same way in all locales with German rules (country code 49).
129  *
130  * Regions currently supported are:
131  *
132  * <ul>
133  * <li>NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations
134  * <li>UK
135  * <li>Republic of Ireland
136  * <li>Germany
137  * <li>France
138  * <li>Spain
139  * <li>Italy
140  * <li>Mexico
141  * <li>India
142  * <li>People's Republic of China
143  * <li>Netherlands
144  * <li>Belgium
145  * <li>Luxembourg
146  * <li>Australia
147  * <li>New Zealand
148  * <li>Singapore
149  * <li>Korea
150  * <li>Japan
151  * <li>Russia
152  * <li>Brazil
153  * </ul>
154  *
155  * @constructor
156  * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone
157  * number instance to copy
158  * @param {Object=} options options that guide the parser in parsing the number
159  */
160 var PhoneNumber = function(number, options) {
161     var stateData,
162         regionSettings;
163 
164     this.sync = true;
165     this.loadParams = {};
166 
167 
168     if (options) {
169         if (typeof(options.sync) === 'boolean') {
170             this.sync = options.sync;
171         }
172 
173         if (options.loadParams) {
174             this.loadParams = options.loadParams;
175         }
176 
177         if (typeof(options.onLoad) === 'function') {
178             /**
179              * @private
180              * @type {function(PhoneNumber)}
181              */
182             this.onLoad = options.onLoad;
183         }
184     } else {
185         options = {sync: true};
186     }
187 
188     if (!number || (typeof number === "string" && number.length === 0)) {
189         if (typeof(options.onLoad) === 'function') {
190             options.onLoad(undefined);
191         }
192 
193         return this;
194     }
195 
196     if (typeof number === "object") {
197         /**
198          * The vertical service code. These are codes that typically
199          * start with a star or hash, like "*69" for "dial back the
200          * last number that called me".
201          * @type {string|undefined}
202          */
203         this.vsc = number.vsc;
204 
205         /**
206          * The international direct dialing prefix. This is always
207          * followed by the country code.
208          * @type {string}
209          */
210         this.iddPrefix = number.iddPrefix;
211 
212         /**
213          * The unique IDD country code for the country where the
214          * phone number is serviced.
215          * @type {string|undefined}
216          */
217         this.countryCode = number.countryCode;
218 
219         /**
220          * The digits required to access the trunk.
221          * @type {string|undefined}
222          */
223         this.trunkAccess = number.trunkAccess;
224 
225         /**
226          * The carrier identification code used to identify
227          * alternate long distance or international carriers.
228          * @type {string|undefined}
229          */
230         this.cic = number.cic;
231 
232         /**
233          * Identifies an emergency number that is typically
234          * short, such as "911" in North America or "112" in
235          * many other places in the world.
236          * @type {string|undefined}
237          */
238         this.emergency = number.emergency;
239 
240         /**
241          * The prefix of the subscriber number that indicates
242          * that this is the number of a mobile phone.
243          * @type {string|undefined}
244          */
245         this.mobilePrefix = number.mobilePrefix;
246 
247         /**
248          * The prefix that identifies this number as commercial
249          * service number.
250          * @type {string|undefined}
251          */
252         this.serviceCode = number.serviceCode;
253 
254         /**
255          * The area code prefix of a land line number.
256          * @type {string|undefined}
257          */
258         this.areaCode = number.areaCode;
259 
260         /**
261          * The unique number associated with the subscriber
262          * of this phone.
263          * @type {string|undefined}
264          */
265         this.subscriberNumber = number.subscriberNumber;
266 
267         /**
268          * The direct dial extension number.
269          * @type {string|undefined}
270          */
271         this.extension = number.extension;
272 
273         /**
274          * @private
275          * @type {boolean}
276          */
277         this.invalid = number.invalid;
278 
279         if (number.plan && number.locale) {
280             /**
281              * @private
282              * @type {NumberingPlan}
283              */
284             this.plan = number.plan;
285 
286             /**
287              * @private
288              * @type {PhoneLocale}
289              */
290             this.locale = number.locale;
291 
292             /**
293              * @private
294              * @type {NumberingPlan}
295              */
296             this.destinationPlan = number.destinationPlan;
297 
298             /**
299              * @private
300              * @type {PhoneLocale}
301              */
302             this.destinationLocale = number.destinationLocale;
303 
304             if (options && typeof(options.onLoad) === 'function') {
305                 options.onLoad(this);
306             }
307             return;
308         }
309     }
310 
311     new PhoneLocale({
312         locale: options && options.locale,
313         mcc: options && options.mcc,
314         sync: this.sync,
315         loadParams: this.loadParams,
316         onLoad: ilib.bind(this, function(loc) {
317             this.locale = this.destinationLocale = loc;
318             new NumberingPlan({
319                 locale: this.locale,
320                 sync: this.sync,
321                 loadParms: this.loadParams,
322                 onLoad: ilib.bind(this, function (plan) {
323                     this.plan = this.destinationPlan = plan;
324 
325                     if (typeof number === "object") {
326                         // the copy constructor code above did not find the locale
327                         // or plan before, but now they are loaded, so we can return
328                         // already without going further
329                         if (typeof(options.onLoad) === "function") {
330                             options.onLoad(this);
331                         }
332                         return;
333                     }
334                     Utils.loadData({
335                         name: "states.json",
336                         object: "PhoneNumber",
337                         locale: this.locale,
338                         sync: this.sync,
339                         loadParams: JSUtils.merge(this.loadParams, {
340                             returnOne: true
341                         }),
342                         callback: ilib.bind(this, function (stdata) {
343                             if (!stdata) {
344                                 stdata = PhoneNumber._defaultStates;
345                             }
346 
347                             stateData = stdata;
348 
349                             regionSettings = {
350                                 stateData: stateData,
351                                 plan: plan,
352                                 handler: PhoneHandlerFactory(this.locale, plan)
353                             };
354 
355                             // use ^ to indicate the beginning of the number, because certain things only match at the beginning
356                             number = "^" + number.replace(/\^/g, '');
357                             number = PhoneNumber._stripFormatting(number);
358 
359                             this._parseNumber(number, regionSettings, options);
360                         })
361                     });
362                 })
363             });
364         })
365     });
366 };
367 
368 /**
369  * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts:
370  *
371  * <ol>
372  * <li>mcc - Mobile Country Code, which identifies the country where the phone is currently receiving
373  * service.
374  * <li>mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone
375  * <li>msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on
376  * the network, which usually maps to an account/subscriber in the carrier's database.
377  * </ol>
378  *
379  * Because this function may need to load data to identify the above parts, you can pass an options
380  * object that controls how the data is loaded. The options may contain any of the following properties:
381  *
382  * <ul>
383  * <li>onLoad - a callback function to call when the parsing is done. When the onLoad option is given,
384  * this method will attempt to load the locale data using the ilib loader callback. When it is done
385  * (even if the data is already preassembled), the onLoad function is called with the parsing results
386  * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or
387  * asynchronous loading or a mix of the above.
388  * <li>sync - tell whether to load any missing locale data synchronously or asynchronously. If this
389  * option is given as "false", then the "onLoad" callback must be given, as the results returned from
390  * this constructor will not be usable for a while.
391  * <li><i>loadParams</i> - an object containing parameters to pass to the loader callback function
392  * when locale data is missing. The parameters are not interpretted or modified in any way. They are
393  * simply passed along. The object may contain any property/value pairs as long as the calling code is in
394  * agreement with the loader callback function as to what those parameters mean.
395  * </ul>
396  *
397  * @static
398  * @param {string} imsi IMSI number to parse
399  * @param {Object} options options controlling the loading of the locale data
400  * @return {{mcc:string,mnc:string,msin:string}|undefined} components of the IMSI number, when the locale data
401  * is loaded synchronously, or undefined if asynchronous
402  */
403 PhoneNumber.parseImsi = function(imsi, options) {
404     var sync = true,
405         loadParams = {},
406         fields = {};
407 
408     if (!imsi) {
409         if (options && typeof(options.onLoad) === 'function') {
410             options.onLoad(undefined);
411         }
412         return undefined;
413     }
414 
415     if (options) {
416         if (typeof(options.sync) !== 'undefined') {
417             sync = !!options.sync;
418         }
419 
420         if (options.loadParams) {
421             loadParams = options.loadParams;
422         }
423     }
424 
425     if (ilib.data.mnc) {
426         fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi);
427 
428         if (options && typeof(options.onLoad) === 'function') {
429             options.onLoad(fields);
430         }
431     } else {
432         Utils.loadData({
433             name: "mnc.json",
434             object: "PhoneNumber",
435             nonlocale: true,
436             sync: sync,
437             loadParams: loadParams,
438             callback: ilib.bind(this, function(data) {
439                 ilib.data.mnc = data;
440                 fields = PhoneNumber._parseImsi(data, imsi);
441 
442                 if (options && typeof(options.onLoad) === 'function') {
443                     options.onLoad(fields);
444                 }
445             })
446         });
447     }
448     return fields;
449 };
450 
451 
452 /**
453  * @static
454  * @protected
455  */
456 PhoneNumber._parseImsi = function(data, imsi) {
457     var ch,
458         i,
459         currentState,
460         end,
461         handlerMethod,
462         newState,
463         fields = {},
464         lastLeaf,
465         consumed = 0;
466 
467     currentState = data;
468     if (!currentState) {
469         // can't parse anything
470         return undefined;
471     }
472 
473     i = 0;
474     while (i < imsi.length) {
475         ch = PhoneNumber._getCharacterCode(imsi.charAt(i));
476         // console.info("parsing char " + imsi.charAt(i) + " code: " + ch);
477         if (ch >= 0) {
478             newState = currentState.s && currentState.s[ch];
479 
480             if (typeof(newState) === 'object') {
481                 if (typeof(newState.l) !== 'undefined') {
482                     // save for latter if needed
483                     lastLeaf = newState;
484                     consumed = i;
485                 }
486                 // console.info("recognized digit " + ch + " continuing...");
487                 // recognized digit, so continue parsing
488                 currentState = newState;
489                 i++;
490             } else {
491                 if ((typeof(newState) === 'undefined' || newState === 0 ||
492                     (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) &&
493                      lastLeaf) {
494                     // this is possibly a look-ahead and it didn't work...
495                     // so fall back to the last leaf and use that as the
496                     // final state
497                     newState = lastLeaf;
498                     i = consumed;
499                 }
500 
501                 if ((typeof(newState) === 'number' && newState) ||
502                     (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) {
503                     // final state
504                     var stateNumber = typeof(newState) === 'number' ? newState : newState.l;
505                     handlerMethod = PhoneNumber._states[stateNumber];
506 
507                     // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i);
508 
509                     // deal with syntactic ambiguity by using the "special" end state instead of "area"
510                     if ( handlerMethod === "area" ) {
511                         end = i+1;
512                     } else {
513                         // unrecognized imsi, so just assume the mnc is 3 digits
514                         end = 6;
515                     }
516 
517                     fields.mcc = imsi.substring(0,3);
518                     fields.mnc = imsi.substring(3,end);
519                     fields.msin = imsi.substring(end);
520 
521                     return fields;
522                 } else {
523                     // parse error
524                     if (imsi.length >= 6) {
525                         fields.mcc = imsi.substring(0,3);
526                         fields.mnc = imsi.substring(3,6);
527                         fields.msin = imsi.substring(6);
528                     }
529                     return fields;
530                 }
531             }
532         } else if (ch === -1) {
533             // non-transition character, continue parsing in the same state
534             i++;
535         } else {
536             // should not happen
537             // console.info("skipping character " + ch);
538             // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it.
539             i++;
540         }
541     }
542 
543     if (i >= imsi.length && imsi.length >= 6) {
544         // we reached the end of the imsi, but did not finish recognizing anything.
545         // Default to last resort and assume 3 digit mnc
546         fields.mcc = imsi.substring(0,3);
547         fields.mnc = imsi.substring(3,6);
548         fields.msin = imsi.substring(6);
549     } else {
550         // unknown or not enough characters for a real imsi
551         fields = undefined;
552     }
553 
554     // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields));
555     return fields;
556 };
557 
558 /**
559  * @static
560  * @private
561  */
562 PhoneNumber._stripFormatting = function(str) {
563     var ret = "";
564     var i;
565 
566     for (i = 0; i < str.length; i++) {
567         if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) {
568             ret += str.charAt(i);
569         }
570     }
571     return ret;
572 };
573 
574 /**
575  * @static
576  * @protected
577  */
578 PhoneNumber._getCharacterCode = function(ch) {
579     if (ch >= '0' && ch <= '9') {
580             return ch - '0';
581         }
582     switch (ch) {
583     case '+':
584         return 10;
585     case '*':
586         return 11;
587     case '#':
588         return 12;
589     case '^':
590         return 13;
591     case 'p':        // pause chars
592     case 'P':
593     case 't':
594     case 'T':
595     case 'w':
596     case 'W':
597         return -1;
598     case 'x':
599     case 'X':
600     case ',':
601     case ';':        // extension char
602         return -1;
603     }
604     return -2;
605 };
606 
607 /**
608  * @private
609  */
610 PhoneNumber._states = [
611     "none",
612     "unknown",
613     "plus",
614     "idd",
615     "cic",
616     "service",
617     "cell",
618     "area",
619     "vsc",
620     "country",
621     "personal",
622     "special",
623     "trunk",
624     "premium",
625     "emergency",
626     "service2",
627     "service3",
628     "service4",
629     "cic2",
630     "cic3",
631     "start",
632     "local"
633 ];
634 
635 /**
636  * @private
637  */
638 PhoneNumber._fieldOrder = [
639     "vsc",
640     "iddPrefix",
641     "countryCode",
642     "trunkAccess",
643     "cic",
644     "emergency",
645     "mobilePrefix",
646     "serviceCode",
647     "areaCode",
648     "subscriberNumber",
649     "extension"
650 ];
651 
652 PhoneNumber._defaultStates = {
653     "s": [
654         0,
655         21,  // 1 -> local
656         21,  // 2 -> local
657         21,  // 3 -> local
658         21,  // 4 -> local
659         21,  // 5 -> local
660         21,  // 6 -> local
661         21,  // 7 -> local
662         21,  // 8 -> local
663         21,  // 9 -> local
664         0,0,0,
665         { // ^
666             "s": [
667                 { // ^0
668                     "s": [3], // ^00 -> idd
669                     "l": 12   // ^0  -> trunk
670                 },
671                 21,  // ^1 -> local
672                 21,  // ^2 -> local
673                 21,  // ^3 -> local
674                 21,  // ^4 -> local
675                 21,  // ^5 -> local
676                 21,  // ^6 -> local
677                 21,  // ^7 -> local
678                 21,  // ^8 -> local
679                 21,  // ^9 -> local
680                 2    // ^+ -> plus
681             ]
682         }
683     ]
684 };
685 
686 PhoneNumber.prototype = {
687     /**
688      * @protected
689      * @param {string} number
690      * @param {Object} regionData
691      * @param {Object} options
692      * @param {string} countryCode
693      */
694     _parseOtherCountry: function(number, regionData, options, countryCode) {
695         new PhoneLocale({
696             locale: this.locale,
697             countryCode: countryCode,
698             sync: this.sync,
699             loadParms: this.loadParams,
700             onLoad: ilib.bind(this, function (loc) {
701                 /*
702                  * this.locale is the locale where this number is being parsed,
703                  * and is used to parse the IDD prefix, if any, and this.destinationLocale is
704                  * the locale of the rest of this number after the IDD prefix.
705                  */
706                 /** @type {PhoneLocale} */
707                 this.destinationLocale = loc;
708 
709                 Utils.loadData({
710                     name: "states.json",
711                     object: "PhoneNumber",
712                     locale: this.destinationLocale,
713                     sync: this.sync,
714                     loadParams: JSUtils.merge(this.loadParams, {
715                         returnOne: true
716                     }),
717                     callback: ilib.bind(this, function (stateData) {
718                         if (!stateData) {
719                             stateData = PhoneNumber._defaultStates;
720                         }
721 
722                         new NumberingPlan({
723                             locale: this.destinationLocale,
724                             sync: this.sync,
725                             loadParms: this.loadParams,
726                             onLoad: ilib.bind(this, function (plan) {
727                                 /*
728                                  * this.plan is the plan where this number is being parsed,
729                                  * and is used to parse the IDD prefix, if any, and this.destinationPlan is
730                                  * the plan of the rest of this number after the IDD prefix in the
731                                  * destination locale.
732                                  */
733                                 /** @type {NumberingPlan} */
734                                 this.destinationPlan = plan;
735 
736                                 var regionSettings = {
737                                     stateData: stateData,
738                                     plan: plan,
739                                     handler: PhoneHandlerFactory(this.destinationLocale, plan)
740                                 };
741 
742                                 // for plans that do not skip the trunk code when dialing from
743                                 // abroad, we need to treat the number from here on in as if it
744                                 // were parsing a local number from scratch. That way, the parser
745                                 // does not get confused between parts of the number at the
746                                 // beginning of the number, and parts in the middle.
747                                 if (!plan.getSkipTrunk()) {
748                                     number = '^' + number;
749                                 }
750 
751                                 // recursively call the parser with the new states data
752                                 // to finish the parsing
753                                 this._parseNumber(number, regionSettings, options);
754                             })
755                         });
756                     })
757                 });
758             })
759         });
760     },
761 
762     /**
763      * @protected
764      * @param {string} number
765      * @param {Object} regionData
766      * @param {Object} options
767      */
768     _parseNumber: function(number, regionData, options) {
769         var i, ch,
770             regionSettings,
771             newState,
772             dot,
773             handlerMethod,
774             result,
775             currentState = regionData.stateData,
776             lastLeaf = undefined,
777             consumed = 0;
778 
779         regionSettings = regionData;
780         dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java
781 
782         i = 0;
783         while (i < number.length) {
784             ch = PhoneNumber._getCharacterCode(number.charAt(i));
785             if (ch >= 0) {
786                 // newState = stateData.states[state][ch];
787                 newState = currentState.s && currentState.s[ch];
788 
789                 if (!newState && currentState.s && currentState.s[dot]) {
790                     newState = currentState.s[dot];
791                 }
792 
793                 if (typeof(newState) === 'object' && i+1 < number.length) {
794                     if (typeof(newState.l) !== 'undefined') {
795                         // this is a leaf node, so save that for later if needed
796                         lastLeaf = newState;
797                         consumed = i;
798                     }
799                     // console.info("recognized digit " + ch + " continuing...");
800                     // recognized digit, so continue parsing
801                     currentState = newState;
802                     i++;
803                 } else {
804                     if ((typeof(newState) === 'undefined' || newState === 0 ||
805                         (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) &&
806                          lastLeaf) {
807                         // this is possibly a look-ahead and it didn't work...
808                         // so fall back to the last leaf and use that as the
809                         // final state
810                         newState = lastLeaf;
811                         i = consumed;
812                         consumed = 0;
813                         lastLeaf = undefined;
814                     }
815 
816                     if ((typeof(newState) === 'number' && newState) ||
817                         (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) {
818                         // final state
819                         var stateNumber = typeof(newState) === 'number' ? newState : newState.l;
820                         handlerMethod = PhoneNumber._states[stateNumber];
821 
822                         if (number.charAt(0) === '^') {
823                             result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings);
824                         } else {
825                             result = regionSettings.handler[handlerMethod](number, i, this, regionSettings);
826                         }
827 
828                         // reparse whatever is left
829                         number = result.number;
830                         i = consumed = 0;
831                         lastLeaf = undefined;
832 
833                         //console.log("reparsing with new number: " +  number);
834                         currentState = regionSettings.stateData;
835                         // if the handler requested a special sub-table, use it for this round of parsing,
836                         // otherwise, set it back to the regular table to continue parsing
837 
838                         if (result.countryCode !== undefined) {
839                             this._parseOtherCountry(number, regionData, options, result.countryCode);
840                             // don't process any further -- let the work be done in the onLoad callbacks
841                             return;
842                         } else if (result.table !== undefined) {
843                             Utils.loadData({
844                                 name: result.table + ".json",
845                                 object: "PhoneNumber",
846                                 nonlocale: true,
847                                 sync: this.sync,
848                                 loadParams: this.loadParams,
849                                 callback: ilib.bind(this, function (data) {
850                                     if (!data) {
851                                         data = PhoneNumber._defaultStates;
852                                     }
853 
854                                     regionSettings = {
855                                         stateData: data,
856                                         plan: regionSettings.plan,
857                                         handler: regionSettings.handler
858                                     };
859 
860                                     // recursively call the parser with the new states data
861                                     // to finish the parsing
862                                     this._parseNumber(number, regionSettings, options);
863                                 })
864                             });
865                             // don't process any further -- let the work be done in the onLoad callbacks
866                             return;
867                         } else if (result.skipTrunk !== undefined) {
868                             ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode());
869                             currentState = currentState.s && currentState.s[ch];
870                         }
871                     } else {
872                         handlerMethod = (typeof(newState) === 'number') ? "none" : "local";
873                         // failed parse. Either no last leaf to fall back to, or there was an explicit
874                         // zero in the table. Put everything else in the subscriberNumber field as the
875                         // default place
876                         if (number.charAt(0) === '^') {
877                             result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings);
878                         } else {
879                             result = regionSettings.handler[handlerMethod](number, i, this, regionSettings);
880                         }
881                         break;
882                     }
883                 }
884             } else if (ch === -1) {
885                 // non-transition character, continue parsing in the same state
886                 i++;
887             } else {
888                 // should not happen
889                 // console.info("skipping character " + ch);
890                 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it.
891                 i++;
892             }
893         }
894         if (i >= number.length && currentState !== regionData.stateData) {
895             handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local";
896             // we reached the end of the phone number, but did not finish recognizing anything.
897             // Default to last resort and put everything that is left into the subscriber number
898             //console.log("Reached end of number before parsing was complete. Using handler for method none.")
899             if (number.charAt(0) === '^') {
900                 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings);
901             } else {
902                 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings);
903             }
904         }
905 
906         // let the caller know we are done parsing
907         if (this.onLoad) {
908             this.onLoad(this);
909         }
910     },
911     /**
912      * @protected
913      */
914     _getPrefix: function() {
915         return this.areaCode || this.serviceCode || this.mobilePrefix || "";
916     },
917 
918     /**
919      * @protected
920      */
921     _hasPrefix: function() {
922         return (this._getPrefix() !== "");
923     },
924 
925     /**
926      * Exclusive or -- return true, if one is defined and the other isn't
927      * @protected
928      */
929     _xor : function(left, right) {
930         if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) {
931             return false;
932         } else {
933             return true;
934         }
935     },
936 
937     /**
938      * return a version of the phone number that contains only the dialable digits in the correct order
939      * @protected
940      */
941     _join: function () {
942         var fieldName, formatted = "";
943 
944         try {
945             for (var field in PhoneNumber._fieldOrder) {
946                 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') {
947                     fieldName = PhoneNumber._fieldOrder[field];
948                     // console.info("normalize: formatting field " + fieldName);
949                     if (this[fieldName] !== undefined) {
950                         formatted += this[fieldName];
951                     }
952                 }
953             }
954         } catch ( e ) {
955             //console.warn("caught exception in _join: " + e);
956             throw e;
957         }
958         return formatted;
959     },
960 
961     /**
962      * This routine will compare the two phone numbers in an locale-sensitive
963      * manner to see if they possibly reference the same phone number.<p>
964      *
965      * In many places,
966      * there are multiple ways to reach the same phone number. In North America for
967      * example, you might have a number with the trunk access code of "1" and another
968      * without, and they reference the exact same phone number. This is considered a
969      * strong match. For a different pair of numbers, one may be a local number and
970      * the other a full phone number with area code, which may reference the same
971      * phone number if the local number happens to be located in that area code.
972      * However, you cannot say for sure if it is in that area code, so it will
973      * be considered a somewhat weaker match.<p>
974      *
975      * Similarly, in other countries, there are sometimes different ways of
976      * reaching the same destination, and the way that numbers
977      * match depends on the locale.<p>
978      *
979      * The various phone number fields are handled differently for matches. There
980      * are various fields that do not need to match at all. For example, you may
981      * type equally enter "00" or "+" into your phone to start international direct
982      * dialling, so the iddPrefix field does not need to match at all.<p>
983      *
984      * Typically, fields that require matches need to match exactly if both sides have a value
985      * for that field. If both sides specify a value and those values differ, that is
986      * a strong non-match. If one side does not have a value and the other does, that
987      * causes a partial match, because the number with the missing field may possibly
988      * have an implied value that matches the other number. For example, the numbers
989      * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234"
990      * might possibly have the same 650 area code as the first number, and might possibly
991      * not. If both side do not specify a value for a particular field, that field is
992      * considered matching.<p>
993      *
994      * The values of following fields are ignored when performing matches:
995      *
996      * <ul>
997      * <li>vsc
998      * <li>iddPrefix
999      * <li>cic
1000      * <li>trunkAccess
1001      * </ul>
1002      *
1003      * The values of the following fields matter if they do not match:
1004      *
1005      * <ul>
1006      * <li>countryCode - A difference causes a moderately strong problem except for
1007      * certain countries where there is a way to access the same subscriber via IDD
1008      * and via intranetwork dialling
1009      * <li>mobilePrefix - A difference causes a possible non-match
1010      * <li>serviceCode - A difference causes a possible non-match
1011      * <li>areaCode - A difference causes a possible non-match
1012      * <li>subscriberNumber - A difference causes a very strong non-match
1013      * <li>extension - A difference causes a minor non-match
1014      * </ul>
1015      *
1016      * @param {string|PhoneNumber} other other phone number to compare this one to
1017      * @return {number} non-negative integer describing the percentage quality of the
1018      * match. 100 means a very strong match (100%), and lower numbers are less and
1019      * less strong, down to 0 meaning not at all a match.
1020      */
1021     compare: function (other) {
1022         var match = 100,
1023             FRdepartments = {"590":1, "594":1, "596":1, "262":1},
1024             ITcountries = {"378":1, "379":1},
1025             thisPrefix,
1026             otherPrefix,
1027             currentCountryCode = 0;
1028 
1029         if (typeof this.locale.region === "string") {
1030             currentCountryCode = this.locale._mapRegiontoCC(this.locale.region);
1031         }
1032 
1033         // subscriber number must be present and must match
1034         if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) {
1035             return 0;
1036         }
1037 
1038         // extension must match if it is present
1039         if (this._xor(this.extension, other.extension) || this.extension !== other.extension) {
1040             return 0;
1041         }
1042 
1043         if (this._xor(this.countryCode, other.countryCode)) {
1044             // if one doesn't have a country code, give it some demerit points, but if the
1045             // one that has the country code has something other than the current country
1046             // add even more. Ignore the special cases where you can dial the same number internationally or via
1047             // the local numbering system
1048             switch (this.locale.getRegion()) {
1049             case 'FR':
1050                 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) {
1051                     if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) {
1052                         match -= 100;
1053                     }
1054                 } else {
1055                     match -= 16;
1056                 }
1057                 break;
1058             case 'IT':
1059                 if (this.countryCode in ITcountries || other.countryCode in ITcountries) {
1060                     if (this.areaCode !== other.areaCode) {
1061                         match -= 100;
1062                     }
1063                 } else {
1064                     match -= 16;
1065                 }
1066                 break;
1067             default:
1068                 match -= 16;
1069                 if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) ||
1070                     (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) {
1071                     match -= 16;
1072                 }
1073             }
1074         } else if (this.countryCode !== other.countryCode) {
1075             // ignore the special cases where you can dial the same number internationally or via
1076             // the local numbering system
1077             if (other.countryCode === '33' || this.countryCode === '33') {
1078                 // france
1079                 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) {
1080                     if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) {
1081                         match -= 100;
1082                     }
1083                 } else {
1084                     match -= 100;
1085                 }
1086             } else if (this.countryCode === '39' || other.countryCode === '39') {
1087                 // italy
1088                 if (this.countryCode in ITcountries || other.countryCode in ITcountries) {
1089                     if (this.areaCode !== other.areaCode) {
1090                         match -= 100;
1091                     }
1092                 } else {
1093                     match -= 100;
1094                 }
1095             } else {
1096                 match -= 100;
1097             }
1098         }
1099 
1100         if (this._xor(this.serviceCode, other.serviceCode)) {
1101             match -= 20;
1102         } else if (this.serviceCode !== other.serviceCode) {
1103             match -= 100;
1104         }
1105 
1106         if (this._xor(this.mobilePrefix, other.mobilePrefix)) {
1107             match -= 20;
1108         } else if (this.mobilePrefix !== other.mobilePrefix) {
1109             match -= 100;
1110         }
1111 
1112         if (this._xor(this.areaCode, other.areaCode)) {
1113             // one has an area code, the other doesn't, so dock some points. It could be a match if the local
1114             // number in the one number has the same implied area code as the explicit area code in the other number.
1115             match -= 12;
1116         } else if (this.areaCode !== other.areaCode) {
1117             match -= 100;
1118         }
1119 
1120         thisPrefix = this._getPrefix();
1121         otherPrefix = other._getPrefix();
1122 
1123         if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) {
1124             match -= 100;
1125         }
1126 
1127         // make sure we are between 0 and 100
1128         if (match < 0) {
1129             match = 0;
1130         } else if (match > 100) {
1131             match = 100;
1132         }
1133 
1134         return match;
1135     },
1136 
1137     /**
1138      * Determine whether or not the other phone number is exactly equal to the current one.<p>
1139      *
1140      * The difference between the compare method and the equals method is that the compare
1141      * method compares normalized numbers with each other and returns the degree of match,
1142      * whereas the equals operator returns true iff the two numbers contain the same fields
1143      * and the fields are exactly the same. Functions and other non-phone number properties
1144      * are not compared.
1145      * @param {string|PhoneNumber} other another phone number to compare to this one
1146      * @return {boolean} true if the numbers are the same, false otherwise
1147      */
1148     equals: function equals(other) {
1149         if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) {
1150             return false;
1151         }
1152 
1153         var _this = this;
1154         return PhoneNumber._fieldOrder.every(function(field) {
1155             return _this[field] === other[field];
1156         });
1157     },
1158 
1159     /**
1160      * @private
1161      * @param {{
1162      *   mcc:string,
1163      *   defaultAreaCode:string,
1164      *   country:string,
1165      *   networkType:string,
1166      *   assistedDialing:boolean,
1167      *   sms:boolean,
1168      *   manualDialing:boolean
1169      * }} options an object containing options to help in normalizing.
1170      * @param {PhoneNumber} norm
1171      * @param {PhoneLocale} homeLocale
1172      * @param {PhoneLocale} currentLocale
1173      * @param {NumberingPlan} currentPlan
1174      * @param {PhoneLocale} destinationLocale
1175      * @param {NumberingPlan} destinationPlan
1176      * @param {boolean} sync
1177      * @param {Object|undefined} loadParams
1178      */
1179     _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) {
1180         var formatted = "";
1181 
1182         if (!norm.invalid && options && options.assistedDialing) {
1183             // don't normalize things that don't have subscriber numbers. Also, don't normalize
1184             // manually dialed local numbers. Do normalize local numbers in contact entries.
1185             if (norm.subscriberNumber &&
1186                     (!options.manualDialing ||
1187                      norm.iddPrefix ||
1188                      norm.countryCode ||
1189                      norm.trunkAccess)) {
1190                 // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm));
1191                 if (currentLocale.getRegion() !== destinationLocale.getRegion()) {
1192                     // we are currently calling internationally
1193                     if (!norm._hasPrefix() &&
1194                             options.defaultAreaCode &&
1195                             destinationLocale.getRegion() === homeLocale.getRegion() &&
1196                             (!destinationPlan.getFieldLength("minLocalLength") ||
1197                                 norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) {
1198                         // area code is required when dialling from international, but only add it if we are dialing
1199                         // to our home area. Otherwise, the default area code is not valid!
1200                         norm.areaCode = options.defaultAreaCode;
1201                         if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) {
1202                             // some phone systems require the trunk access code, even when dialling from international
1203                             norm.trunkAccess = destinationPlan.getTrunkCode();
1204                         }
1205                     }
1206 
1207                     if (norm.trunkAccess && destinationPlan.getSkipTrunk()) {
1208                         // on some phone systems, the trunk access code is dropped when dialling from international
1209                         delete norm.trunkAccess;
1210                     }
1211 
1212                     // make sure to get the country code for the destination region, not the current region!
1213                     if (options.sms) {
1214                         if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") {
1215                             if (destinationLocale.getRegion() !== "US") {
1216                                 norm.iddPrefix = "011"; // non-standard code to make it go through the US first
1217                                 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion());
1218                             } else if (options.networkType === "cdma") {
1219                                 delete norm.iddPrefix;
1220                                 delete norm.countryCode;
1221                                 if (norm.areaCode) {
1222                                     norm.trunkAccess = "1";
1223                                 }
1224                             } else if (norm.areaCode) {
1225                                 norm.iddPrefix = "+";
1226                                 norm.countryCode = "1";
1227                                 delete norm.trunkAccess;
1228                             }
1229                         } else {
1230                             norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+";
1231                             norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region);
1232                         }
1233                     } else if (norm._hasPrefix() && !norm.countryCode) {
1234                         norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region);
1235                     }
1236 
1237                     if (norm.countryCode && !options.sms) {
1238                         // for CDMA, make sure to get the international dialling access code for the current region, not the destination region
1239                         // all umts carriers support plus dialing
1240                         norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+";
1241                     }
1242                 } else {
1243                     // console.log("normalize: dialing within the country");
1244                     if (options.defaultAreaCode) {
1245                         if (destinationPlan.getPlanStyle() === "open") {
1246                             if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) {
1247                                 // call is not local to this area code, so you have to dial the trunk code and the area code
1248                                 norm.trunkAccess = destinationPlan.getTrunkCode();
1249                             }
1250                         } else {
1251                             // In closed plans, you always have to dial the area code, even if the call is local.
1252                             if (!norm._hasPrefix()) {
1253                                 if (destinationLocale.getRegion() === homeLocale.getRegion()) {
1254                                     norm.areaCode = options.defaultAreaCode;
1255                                     if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) {
1256                                         norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode();
1257                                     }
1258                                 }
1259                             } else {
1260                                 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) {
1261                                     norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode();
1262                                 }
1263                             }
1264                         }
1265                     }
1266 
1267                     if (options.sms &&
1268                             homeLocale.getRegion() === "US" &&
1269                             currentLocale.getRegion() !== "US") {
1270                         norm.iddPrefix = "011"; // make it go through the US first
1271                         if (destinationPlan.getSkipTrunk() && norm.trunkAccess) {
1272                             delete norm.trunkAccess;
1273                         }
1274                     } else if (norm.iddPrefix || norm.countryCode) {
1275                         // we are in our destination country, so strip the international dialling prefixes
1276                         delete norm.iddPrefix;
1277                         delete norm.countryCode;
1278 
1279                         if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) {
1280                             norm.trunkAccess = destinationPlan.getTrunkCode();
1281                         }
1282                     }
1283                 }
1284             }
1285         } else if (!norm.invalid) {
1286             // console.log("normalize: non-assisted normalization");
1287             if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) {
1288                 norm.areaCode = options.defaultAreaCode;
1289             }
1290 
1291             if (!norm.countryCode && norm._hasPrefix()) {
1292                 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion());
1293             }
1294 
1295             if (norm.countryCode) {
1296                 if (options && options.networkType && options.networkType === "cdma") {
1297                     norm.iddPrefix = currentPlan.getIDDCode();
1298                 } else {
1299                     // all umts carriers support plus dialing
1300                     norm.iddPrefix = "+";
1301                 }
1302 
1303                 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) {
1304                     delete norm.trunkAccess;
1305                 } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) {
1306                     norm.trunkAccess = destinationPlan.getTrunkCode();
1307                 }
1308             }
1309         }
1310 
1311         // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm));
1312         formatted = norm._join();
1313 
1314         return formatted;
1315     },
1316 
1317     /**
1318      * @private
1319      * @param {{
1320      *   mcc:string,
1321      *   defaultAreaCode:string,
1322      *   country:string,
1323      *   networkType:string,
1324      *   assistedDialing:boolean,
1325      *   sms:boolean,
1326      *   manualDialing:boolean
1327      * }} options an object containing options to help in normalizing.
1328      * @param {PhoneNumber} norm
1329      * @param {PhoneLocale} homeLocale
1330      * @param {PhoneLocale} currentLocale
1331      * @param {NumberingPlan} currentPlan
1332      * @param {PhoneLocale} destinationLocale
1333      * @param {NumberingPlan} destinationPlan
1334      * @param {boolean} sync
1335      * @param {Object|undefined} loadParams
1336      * @param {function(string)} callback
1337      */
1338     _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) {
1339         var formatted,
1340             tempRegion;
1341 
1342         if (options &&
1343                 options.assistedDialing &&
1344                 !norm.trunkAccess &&
1345                 !norm.iddPrefix &&
1346                 norm.subscriberNumber &&
1347                 norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) {
1348 
1349             // numbers that are too long are sometimes international direct dialed numbers that
1350             // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works.
1351             new PhoneNumber("+" + this._join(), {
1352                 locale: this.locale,
1353                 sync: sync,
1354                 loadParms: loadParams,
1355                 onLoad: ilib.bind(this, function (data) {
1356                     tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode));
1357 
1358                     if (tempRegion && tempRegion !== "XX" && tempRegion !== "SG") {
1359                         // only use it if it is a recognized country code. Singapore (SG) is a special case.
1360                         norm = data;
1361                         destinationLocale = data.destinationLocale;
1362                         destinationPlan = data.destinationPlan;
1363                     }
1364 
1365                     formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams);
1366                     if (typeof(callback) === 'function') {
1367                         callback(formatted);
1368                     }
1369                 })
1370             });
1371         } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) {
1372             // if this number is not valid for the locale it was parsed with, try again with the current locale
1373             // console.log("norm is invalid. Attempting to reparse with the current locale");
1374 
1375             new PhoneNumber(this._join(), {
1376                 locale: currentLocale,
1377                 sync: sync,
1378                 loadParms: loadParams,
1379                 onLoad: ilib.bind(this, function (data) {
1380                     if (data && !data.invalid) {
1381                         norm = data;
1382                     }
1383 
1384                     formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams);
1385                     if (typeof(callback) === 'function') {
1386                         callback(formatted);
1387                     }
1388                 })
1389             });
1390         } else {
1391             formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams);
1392             if (typeof(callback) === 'function') {
1393                 callback(formatted);
1394             }
1395         }
1396     },
1397 
1398     /**
1399      * This function normalizes the current phone number to a canonical format and returns a
1400      * string with that phone number. If parts are missing, this function attempts to fill in
1401      * those parts.<p>
1402      *
1403      * The options object contains a set of properties that can possibly help normalize
1404      * this number by providing "extra" information to the algorithm. The options
1405      * parameter may be null or an empty object if no hints can be determined before
1406      * this call is made. If any particular hint is not
1407      * available, it does not need to be present in the options object.<p>
1408      *
1409      * The following is a list of hints that the algorithm will look for in the options
1410      * object:
1411      *
1412      * <ul>
1413      * <li><i>mcc</i> the mobile carrier code of the current network upon which this
1414      * phone is operating. This is translated into an IDD country code. This is
1415      * useful if the number being normalized comes from CNAP (callerid) and the
1416      * MCC is known.
1417      * <li><i>defaultAreaCode</i> the area code of the phone number of the current
1418      * device, if available. Local numbers in a person's contact list are most
1419      * probably in this same area code.
1420      * <li><i>country</i> the 2 letter ISO 3166 code of the country if it is
1421      * known from some other means such as parsing the physical address of the
1422      * person associated with the phone number, or the from the domain name
1423      * of the person's email address
1424      * <li><i>networkType</i> specifies whether the phone is currently connected to a
1425      * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts".
1426      * If one of those two strings are not specified, or if this property is left off
1427      * completely, this method will assume UMTS.
1428      * </ul>
1429      *
1430      * The following are a list of options that control the behaviour of the normalization:
1431      *
1432      * <ul>
1433      * <li><i>assistedDialing</i> if this is set to true, the number will be normalized
1434      * so that it can dialled directly on the type of network this phone is
1435      * currently connected to. This allows customers to dial numbers or use numbers
1436      * in their contact list that are specific to their "home" region when they are
1437      * roaming and those numbers would not otherwise work with the current roaming
1438      * carrier as they are. The home region is
1439      * specified as the phoneRegion system preference that is settable in the
1440      * regional settings app. With assisted dialling, this method will add or
1441      * remove international direct dialling prefixes and country codes, as well as
1442      * national trunk access codes, as required by the current roaming carrier and the
1443      * home region in order to dial the number properly. If it is not possible to
1444      * construct a full international dialling sequence from the options and hints given,
1445      * this function will not modify the phone number, and will return "undefined".
1446      * If assisted dialling is false or not specified, then this method will attempt
1447      * to add all the information it can to the number so that it is as fully
1448      * specified as possible. This allows two numbers to be compared more easily when
1449      * those two numbers were otherwise only partially specified.
1450      * <li><i>sms</i> set this option to true for the following conditions:
1451      *   <ul>
1452      *   <li>assisted dialing is turned on
1453      *   <li>the phone number represents the destination of an SMS message
1454      *   <li>the phone is UMTS
1455      *   <li>the phone is SIM-locked to its carrier
1456      *   </ul>
1457      * This enables special international direct dialling codes to route the SMS message to
1458      * the correct carrier. If assisted dialling is not turned on, this option has no
1459      * affect.
1460      * <li><i>manualDialing</i> set this option to true if the user is entering this number on
1461      * the keypad directly, and false when the number comes from a stored location like a
1462      * contact entry or a call log entry. When true, this option causes the normalizer to
1463      * not perform any normalization on numbers that look like local numbers in the home
1464      * country. If false, all numbers go through normalization. This option only has an effect
1465      * when the assistedDialing option is true as well, otherwise it is ignored.
1466      * </ul>
1467      *
1468      * If both a set of options and a locale are given, and they offer conflicting
1469      * information, the options will take precedence. The idea is that the locale
1470      * tells you the region setting that the user has chosen (probably in
1471      * firstuse), whereas the the hints are more current information such as
1472      * where the phone is currently operating (the MCC).<p>
1473      *
1474      * This function performs the following types of normalizations with assisted
1475      * dialling turned on:
1476      *
1477      * <ol>
1478      * <li>If the current location of the phone matches the home country, this is a
1479      * domestic call.
1480      *   <ul>
1481      *   <li>Remove any iddPrefix and countryCode fields, as they are not needed
1482      *   <li>Add in a trunkAccess field that may be necessary to call a domestic numbers
1483      *     in the home country
1484      *   </ul>
1485      * <li> If the current location of the phone does not match the home country,
1486      * attempt to form a whole international number.
1487      *   <ul>
1488      *   <li>Add in the area code if it is missing from the phone number and the area code
1489      *     of the current phone is available in the hints
1490      *   <li>Add the country dialling code for the home country if it is missing from the
1491      *     phone number
1492      *   <li>Add or replace the iddPrefix with the correct one for the current country. The
1493      *     phone number will have been parsed with the settings for the home country, so
1494      *     the iddPrefix may be incorrect for the
1495      *     current country. The iddPrefix for the current country can be "+" if the phone
1496      *     is connected to a UMTS network, and either a "+" or a country-dependent
1497      *     sequences of digits for CDMA networks.
1498      *   </ul>
1499      * </ol>
1500      *
1501      * This function performs the following types of normalization with assisted
1502      * dialling turned off:
1503      *
1504      * <ul>
1505      * <li>Normalize the international direct dialing prefix to be a plus or the
1506      * international direct dialling access code for the current country, depending
1507      * on the network type.
1508      * <li>If a number is a local number (ie. it is missing its area code),
1509      * use a default area code from the hints if available. CDMA phones always know their area
1510      * code, and GSM/UMTS phones know their area code in many instances, but not always
1511      * (ie. not on Vodaphone or Telcel phones). If the default area code is not available,
1512      * do not add it.
1513      * <li>In assisted dialling mode, if a number is missing its country code,
1514      * use the current MCC number if
1515      * it is available to figure out the current country code, and prepend that
1516      * to the number. If it is not available, leave it off. Also, use that
1517      * country's settings to parse the number instead of the current format
1518      * locale.
1519      * <li>For North American numbers with an area code but no trunk access
1520      * code, add in the trunk access code.
1521      * <li>For other countries, if the country code is added in step 3, remove the
1522      * trunk access code when required by that country's conventions for
1523      * international calls. If the country requires a trunk access code for
1524      * international calls and it doesn't exist, add one.
1525      * </ul>
1526      *
1527      * This method modifies the current object, and also returns a string
1528      * containing the normalized phone number that can be compared directly against
1529      * other normalized numbers. The canonical format for phone numbers that is
1530      * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string
1531      * of dialable digits.
1532      *
1533      * @param {{
1534      *   mcc:string,
1535      *   defaultAreaCode:string,
1536      *   country:string,
1537      *   networkType:string,
1538      *   assistedDialing:boolean,
1539      *   sms:boolean,
1540      *   manualDialing:boolean
1541      * }} options an object containing options to help in normalizing.
1542      * @return {string|undefined} the normalized string, or undefined if the number
1543      * could not be normalized
1544      */
1545     normalize: function(options) {
1546         var norm,
1547             sync = true,
1548             loadParams = {};
1549 
1550 
1551         if (options) {
1552             if (typeof(options.sync) !== 'undefined') {
1553                 sync = !!options.sync;
1554             }
1555 
1556             if (options.loadParams) {
1557                 loadParams = options.loadParams;
1558             }
1559         }
1560 
1561         // Clone this number, so we don't mess with the original.
1562         // No need to do this asynchronously because it's a copy constructor which doesn't
1563         // load any extra files.
1564         norm = new PhoneNumber(this);
1565 
1566         var normalized;
1567 
1568         if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) {
1569             new PhoneLocale({
1570                 mcc: options.mcc,
1571                 countryCode: options.countryCode,
1572                 locale: this.locale,
1573                 sync: sync,
1574                 loadParams: loadParams,
1575                 onLoad: ilib.bind(this, function(loc) {
1576                     new NumberingPlan({
1577                         locale: loc,
1578                         sync: sync,
1579                         loadParms: loadParams,
1580                         onLoad: ilib.bind(this, function (plan) {
1581                             this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) {
1582                                 normalized = fmt;
1583 
1584                                 if (options && typeof(options.onLoad) === 'function') {
1585                                     options.onLoad(fmt);
1586                                 }
1587                             });
1588                         })
1589                     });
1590                 })
1591             });
1592         } else {
1593             this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) {
1594                 normalized = fmt;
1595 
1596                 if (options && typeof(options.onLoad) === 'function') {
1597                     options.onLoad(fmt);
1598                 }
1599             });
1600         }
1601 
1602         // return the value for the synchronous case
1603         return normalized;
1604     }
1605 };
1606 
1607 module.exports = PhoneNumber;