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("./ilib.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 !== "unknown" && 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;