PageSourceSearch

https://textiledata.ru/bitrix/js/main/phonenumber/phonenumber.js?168501976360787

js textiledata.ru collected 2026-09-26 12:14:45 UTC 60,787 bytes, 2,189 lines download raw bytes

1;(function()
2{
3	if (BX.PhoneNumber)
4		return;
5
6	var parserInstance;
7
8	var metadataPromise = null;
9	var metadataLoaded = false;
10	var metadataUrl = '/bitrix/js/main/phonenumber/metadata.json';
11	var ajaxUrl = '/bitrix/tools/phone_number.php';
12
13	var metadata = {};
14	var codeToCountries;
15
16	var MAX_LENGTH_COUNTRY_CODE = 3; // The maximum length of the country calling code.
17	var MIN_LENGTH_FOR_NSN = 2; // The minimum length of the national significant number.
18	var MAX_LENGTH_FOR_NSN = 17; // The ITU says the maximum length should be 15, but one can find longer numbers in Germany.
19
20	/* We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious input from consuming CPU.*/
21	var MAX_INPUT_STRING_LENGTH = 250;
22
23	var plusChar = '+';
24
25	/* Digits accepted in phone numbers (ascii, fullwidth, arabic-indic, and eastern arabic digits). */
26	var validDigits = '0-9';
27	var dashes = '-';
28	var slashes = '\/';
29	var dot = '.';
30	var whitespace = '\\s';
31	var brackets = '()\\[\\]';
32	var tildes = '~';
33	var extensionSeparators = ';#';
34	var extensionSymbols = ',';
35
36	var phoneNumberStartPattern = '[' + plusChar + validDigits + ']';
37	var afterPhoneNumberEndPattern = '[^' + validDigits + extensionSeparators + extensionSymbols + ']+$';
38	var minLengthPhoneNumberPattern = '[' + validDigits + ']{' + MIN_LENGTH_FOR_NSN + '}';
39	var validPunctuation = dashes + slashes + dot + whitespace + brackets + tildes + extensionSeparators + extensionSymbols;
40	var significantChars = validDigits + plusChar + extensionSeparators + extensionSymbols;
41
42	var validPhoneNumber =
43		'[' + plusChar + ']{0,1}' +
44		'(?:' +
45		'[' + validPunctuation + ']*' +
46		'[' + validDigits + ']' +
47		'){3,}' +
48		'[' +
49		validPunctuation +
50		validDigits +
51		']*';
52
53	var validPhoneNumberPattern =
54		'^(?:'+
55		// Either a short two-digit-only phone number
56			'^' + minLengthPhoneNumberPattern +'$' +
57		// Or a longer fully parsed phone number (min 3 characters)
58		'|' + '^' + validPhoneNumber + '$' +
59		')$';
60
61	var loadMetadata = function()
62	{
63		if(metadataLoaded)
64		{
65			var result = new BX.Promise();
66			result.fulfill({
67				codeToCountries: codeToCountries,
68				metadata: metadata
69			});
70			return result;
71		}
72		else if(metadataPromise)
73		{
74			return metadataPromise;
75		}
76		else
77		{
78			metadataPromise = new BX.Promise();
79
80			BX.ajax.load({
81				'url': metadataUrl,
82				'type': 'json',
83				'callback': function(data)
84				{
85					codeToCountries = data.codeToCountries;
86					metadata = data.metadata;
87					data.metadata.forEach(function(metadataRecord)
88					{
89						metadata[metadataRecord['id']] = metadataRecord;
90					});
91					metadataLoaded = true;
92					metadataPromise.fulfill({
93						codeToCountries: codeToCountries,
94						metadata: metadata
95					});
96				}
97			});
98			return metadataPromise;
99		}
100	};
101
102	BX.PhoneNumber = function()
103	{
104		this.rawNumber = null;
105		this.country = null;
106
107		this.valid = false;
108		this.countryCode = null;
109		this.nationalNumber = null;
110		this.numberType = null;
111		this.extension = null;
112		this.extensionSeparator = null;
113
114		this.international = false;
115		this.nationalPrefix = null;
116		this.hasPlusChar = false;
117	};
118
119	BX.PhoneNumber.Format = {
120		'E164': 'E.164',
121		'INTERNATIONAL': 'International',
122		'NATIONAL': 'National'
123	};
124
125	BX.PhoneNumber.getDefaultCountry  = function()
126	{
127		return BX.message('phone_number_default_country');
128	};
129
130	BX.PhoneNumber.getUserDefaultCountry = function()
131	{
132		return BX.message('user_default_country');
133	};
134
135	BX.PhoneNumber.getIncompleteFormatter = function(defaultCountry)
136	{
137		var result = new BX.Promise();
138
139		if(metadataLoaded)
140		{
141			result.fulfill(new BX.PhoneNumber.IncompleteFormatter(defaultCountry));
142		}
143		else
144		{
145			loadMetadata().then(function()
146			{
147				result.fulfill(new BX.PhoneNumber.IncompleteFormatter(defaultCountry));
148			});
149		}
150
151		return result;
152	};
153
154	BX.PhoneNumber.getValidNumberPattern = function()
155	{
156		return validPhoneNumber;
157	};
158
159	BX.PhoneNumber.getValidNumberRegex = function()
160	{
161		return new RegExp(validPhoneNumber);
162	};
163
164	BX.PhoneNumber.prototype.format = function(formatType)
165	{
166		if(this.valid)
167		{
168			if(!formatType)
169			{
170				return BX.PhoneNumberFormatter.formatOriginal(this);
171			}
172			else
173			{
174				return BX.PhoneNumberFormatter.format(this, formatType)
175			}
176		}
177		else
178		{
179			if(ShortNumberFormatter.isApplicable(this.getRawNumber()))
180			{
181				return ShortNumberFormatter.format(this.getRawNumber());
182			}
183			else
184			{
185				return this.rawNumber;
186			}
187		}
188	};
189
190	BX.PhoneNumber.prototype.getRawNumber = function()
191	{
192		return this.rawNumber;
193	};
194
195	BX.PhoneNumber.prototype.setRawNumber = function(rawNumber)
196	{
197		this.rawNumber = rawNumber;
198	};
199
200	BX.PhoneNumber.prototype.getCountry = function()
201	{
202		return this.country;
203	};
204
205	BX.PhoneNumber.prototype.setCountry = function(country)
206	{
207		this.country = country;
208	};
209
210	BX.PhoneNumber.prototype.isValid = function()
211	{
212		return this.valid;
213	};
214
215	BX.PhoneNumber.prototype.setValid = function(valid)
216	{
217		this.valid = valid;
218	};
219
220	BX.PhoneNumber.prototype.getCountryCode = function()
221	{
222		return this.countryCode;
223	};
224
225	BX.PhoneNumber.prototype.setCountryCode = function(countryCode)
226	{
227		this.countryCode = countryCode;
228	};
229
230	BX.PhoneNumber.prototype.getNationalNumber = function()
231	{
232		return this.nationalNumber;
233	};
234
235	BX.PhoneNumber.prototype.setNationalNumber = function(nationalNumber)
236	{
237		this.nationalNumber = nationalNumber;
238	};
239
240	BX.PhoneNumber.prototype.getNumberType = function()
241	{
242		return this.numberType;
243	};
244
245	BX.PhoneNumber.prototype.setNumberType = function(numberType)
246	{
247		this.numberType = numberType;
248	};
249
250	BX.PhoneNumber.prototype.hasExtension = function()
251	{
252		return !!this.extension;
253	};
254
255	BX.PhoneNumber.prototype.getExtension = function()
256	{
257		return this.extension;
258	};
259
260	BX.PhoneNumber.prototype.setExtension = function(extension)
261	{
262		this.extension = extension;
263	};
264
265	BX.PhoneNumber.prototype.getExtensionSeparator = function()
266	{
267		return this.extensionSeparator;
268	};
269
270	BX.PhoneNumber.prototype.setExtensionSeparator = function(extensionSeparator)
271	{
272		this.extensionSeparator = extensionSeparator;
273	};
274
275	BX.PhoneNumber.prototype.isInternational = function()
276	{
277		return this.international;
278	};
279
280	BX.PhoneNumber.prototype.setInternational = function(international)
281	{
282		this.international = international;
283	};
284
285	BX.PhoneNumber.prototype.getNationalPrefix = function()
286	{
287		return this.nationalPrefix;
288	};
289
290	BX.PhoneNumber.prototype.setNationalPrefix = function(nationalPrefix)
291	{
292		this.nationalPrefix = nationalPrefix;
293	};
294
295	BX.PhoneNumber.prototype.hasPlus = function()
296	{
297		return this.hasPlusChar;
298	};
299
300	BX.PhoneNumber.prototype.setHasPlus = function(hasPlus)
301	{
302		this.hasPlusChar = hasPlus;
303	};
304
305	BX.PhoneNumberParser = function()
306	{
307
308	};
309
310	BX.PhoneNumberParser.getInstance = function()
311	{
312		if(!(parserInstance instanceof BX.PhoneNumberParser))
313			parserInstance = new BX.PhoneNumberParser();
314
315		return parserInstance;
316	};
317
318	BX.PhoneNumberParser.prototype.parse = function(phoneNumber, defaultCountry)
319	{
320		var self = this;
321		var result = new BX.Promise();
322
323		if(!defaultCountry)
324			defaultCountry = BX.PhoneNumber.getDefaultCountry();
325
326		if(metadataLoaded)
327		{
328			result.fulfill(self._realParse(phoneNumber, defaultCountry));
329		}
330		else
331		{
332			loadMetadata().then(function()
333			{
334				result.fulfill(self._realParse(phoneNumber, defaultCountry));
335			});
336		}
337
338		return result;
339	};
340
341	BX.PhoneNumberParser.prototype._realParse = function(phoneNumber, defaultCountry)
342	{
343		var result = new BX.PhoneNumber();
344		result.setRawNumber(phoneNumber);
345
346		var formattedPhoneNumber = _extractFormattedPhoneNumber(phoneNumber);
347		if(!_isViablePhoneNumber(formattedPhoneNumber))
348		{
349			return result;
350		}
351
352		var extensionParseResult = _stripExtension(formattedPhoneNumber);
353		var extension = extensionParseResult.extension;
354		var extensionSeparator = extensionParseResult.extensionSeparator;
355
356		formattedPhoneNumber = extensionParseResult.phoneNumber;
357
358		var parseResult = _parsePhoneNumberAndCountryPhoneCode(formattedPhoneNumber);
359		if(parseResult === false)
360		{
361			return result;
362		}
363
364		var country;
365		var countryCode = parseResult['countryCode'];
366		var localNumber = parseResult['localNumber'];
367		var isInternational;
368		var countryMetadata;
369		var hasPlusChar = false;
370
371		if(countryCode)
372		{
373			// Number in international format, thus we ignore $country parameter
374			isInternational = true;
375			hasPlusChar = true;
376			countryMetadata = _getMetadataByCountryCode(countryCode);
377
378			/*
379			 $country will be set later, because, for example, for NANPA countries
380			 there are several countries corresponding to the same `1` country phone code.
381			 Therefore, to reliably determine the exact country, national number should be parsed first.
382			 */
383			country = null;
384		}
385		else if(!defaultCountry)
386		{
387			return result;
388		}
389		else
390		{
391			// Number in national format or in international format without + sign.
392			country = defaultCountry;
393			countryMetadata = _getCountryMetadata(country);
394			if(!countryMetadata)
395				return result;
396
397			countryCode = countryMetadata['countryCode'];
398			var numberWithoutCountryCode = _stripCountryCode(localNumber, countryMetadata);
399			isInternational = (numberWithoutCountryCode !== localNumber);
400
401			localNumber = numberWithoutCountryCode;
402		}
403
404		if(!countryMetadata)
405		{
406			return result;
407		}
408
409		var numberWithoutNationalPrefix = _stripNationalPrefix(localNumber, 
409countryMetadata);
410
411		var hadNationalPrefix = false;
412		var nationalPrefix = '';
413		if (numberWithoutNationalPrefix !== localNumber)
414		{
415			hadNationalPrefix = _isNumberValid(numberWithoutNationalPrefix, countryMetadata);
416			if(hadNationalPrefix)
417			{
418				nationalPrefix = localNumber.substr(0, localNumber.length - numberWithoutNationalPrefix.length);
419				localNumber = numberWithoutNationalPrefix;
420			}
421		}
422
423		// Sometimes there are several countries corresponding to the same country phone code (e.g. NANPA countries all
424		// having `1` country phone code). Therefore, to reliably determine the exact country, national (significant)
425		// number should have been parsed first.
426		if(country === null)
427		{
428			country = _findCountry(countryCode, localNumber);
429			if(!country)
430			{
431				return result;
432			}
433
434			countryMetadata = _getCountryMetadata(country);
435		}
436
437		// Validate local (significant) number length
438		if(localNumber.length > MAX_LENGTH_FOR_NSN)
439		{
440			return result;
441		}
442
443		var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$');
444		if(!localNumber.match(nationalNumberRegex))
445		{
446			return result;
447		}
448
449		var numberType = _getNumberType(localNumber, country);
450		result.setCountry(country);
451		result.setCountryCode(countryCode);
452		result.setNationalNumber(localNumber);
453		result.setNumberType(numberType);
454		result.setInternational(isInternational);
455		result.setHasPlus(hasPlusChar);
456		result.setNationalPrefix(nationalPrefix);
457		result.setExtension(extension);
458		result.setExtensionSeparator(extensionSeparator);
459		result.setValid(numberType !== false);
460
461		return result;
462	};
463
464	BX.PhoneNumberFormatter = {};
465
466	BX.PhoneNumberFormatter.format = function(number, formatType)
467	{
468		if(!(number instanceof BX.PhoneNumber))
469		{
470			throw new Error("number should be instance of BX.PhoneNumber");
471		}
472
473		if(!metadataLoaded)
474		{
475			throw new Error("Metadata should be loaded prior to calling format");
476		}
477
478		if(!number.isValid())
479			return number.getRawNumber();
480
481		if(formatType === BX.PhoneNumber.Format.E164)
482		{
483			var result = '+' + number.getCountryCode()
484				+ number.getNationalNumber()
485				+ (number.hasExtension() ? number.getExtensionSeparator() + " " + number.getExtension() : "");
486
487			return result;
488		}
489
490		var countryMetadata = _getCountryMetadata(number.getCountry());
491		var isInternational = formatType === BX.PhoneNumber.Format.INTERNATIONAL;
492		var format = this.selectFormatForNumber(number.getNationalNumber(), isInternational, countryMetadata);
493
494		if(format)
495		{
496			var formattedNationalNumber = this.formatNationalNumber(
497				number.getNationalNumber(),
498				isInternational,
499				countryMetadata,
500				format
501			);
502		}
503		else
504		{
505			formattedNationalNumber = number.getNationalNumber();
506		}
507
508		if(number.hasExtension())
509		{
510			formattedNationalNumber += number.getExtensionSeparator() + " " + number.getExtension();
511		}
512
513		if(formatType === BX.PhoneNumber.Format.INTERNATIONAL)
514		{
515			return '+' + number.getCountryCode() + ' ' + formattedNationalNumber;
516		}
517		else if(formatType === BX.PhoneNumber.Format.NATIONAL)
518		{
519			return formattedNationalNumber;
520		}
521
522		return number.getRawNumber();
523	};
524
525	BX.PhoneNumberFormatter.formatOriginal = function(number)
526	{
527		if(!number.isValid())
528			return number.getRawNumber();
529
530		var format = this.selectOriginalFormatForNumber(number);
531		if(!format)
532			return number.getRawNumber();
533
534		var formattedNationalNumber = this.formatNationalNumberWithOriginalFormat(number, format);
535
536		if(number.hasExtension())
537		{
538			formattedNationalNumber += number.getExtensionSeparator() + " " + number.getExtension();
539		}
540
541		if(number.isInternational())
542		{
543			var formattedNumber = (number.hasPlus() ? '+' : '') + number.getCountryCode() + ' ' + formattedNationalNumber;
544		}
545		else
546		{
547			formattedNumber = formattedNationalNumber;
548		}
549
550		// If no digit was inserted/removed/altered in a process of formatting, return the formatted number;
551		var normalizedFormattedNumber = _stripLetters(formattedNumber);
552		var normalizedRawInput = _stripLetters(number.getRawNumber());
553		if (normalizedFormattedNumber !== normalizedRawInput)
554		{
555			formattedNumber = number.getRawNumber();
556		}
557
558		return formattedNumber;
559	};
560
561	BX.PhoneNumberFormatter.selectFormatForNumber = function(nationalNumber, isInternational, countryMetadata)
562	{
563		var availableFormats = _getAvailableFormats(countryMetadata);
564
565		for (var i = 0; i < availableFormats.length; i++)
566		{
567			var format = availableFormats[i];
568			if(isInternational &&  format.hasOwnProperty('intlFormat') && format['intlFormat'] === 'NA')
569				continue;
570
571			if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(nationalNumber, format['leadingDigits']))
572			{
573				continue;
574			}
575
576			var formatPatternRegex = new RegExp('^' + format['pattern'] + '$');
577			if(nationalNumber.match(formatPatternRegex))
578			{
579				return format;
580			}
581		}
582		return false;
583	};
584
585	BX.PhoneNumberFormatter.selectOriginalFormatForNumber = function(number)
586	{
587		var nationalNumber = number.getNationalNumber();
588		var isInternational = number.isInternational();
589		var hasNationalPrefix = number.getNationalPrefix() != '';
590		var countryMetadata = _getCountryMetadata(number.getCountry());
591		var availableFormats = _getAvailableFormats(countryMetadata);
592
593		for (var i = 0; i < availableFormats.length; i++)
594		{
595			var format = availableFormats[i];
596			if(isInternational)
597			{
598				if(format.hasOwnProperty('intlFormat') && format['intlFormat'] === 'NA')
599				{
600					continue;
601				}
602			}
603			else
604			{
605				if(hasNationalPrefix && !_isNationalPrefixSupported(format, countryMetadata))
606				{
607					continue;
608				}
609			}
610
611
612			if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(nationalNumber, format['leadingDigits']))
613			{
614				continue;
615			}
616
617			var formatPatternRegex = new RegExp('^' + format['pattern'] + '$');
618			if(nationalNumber.match(formatPatternRegex))
619			{
620				return format;
621			}
622		}
623		return false;
624	}
625
626	BX.PhoneNumberFormatter.formatNationalNumber = function(nationalNumber, isInternational, countryMetadata, format)
627	{
628		var replaceFormat = (format.hasOwnProperty('intlFormat') && isInternational) ? format['intlFormat'] : format['format'];
629		var patternRegex = new RegExp(format['pattern']);
630
631		if(!isInternational)
632		{
633			var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata);
634			if(nationalPrefixFormattingRule != '')
635			{
636				nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', countryMetadata['nationalPrefix']).replace('$FG', '$1');
637				replaceFormat = replaceFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule);
638			}
639			else
640			{
641				replaceFormat = countryMetadata['nationalPrefix'] + ' ' + replaceFormat;
642			}
643		}
644
645		return nationalNumber.replace(patternRegex, replaceFormat);
646	};
647
648	BX.PhoneNumberFormatter.formatNationalNumberWithOriginalFormat = function(number, format)
649	{
650		var isInternational = number.isInternational();
651		var replaceFormat = (format.hasOwnProperty('intlFormat') && isInternational) ? format['intlFormat'] : format['format'];
652		var patternRegex =  new RegExp(format['pattern']);
653		var nationalNumber = number.getNationalNumber();
654		var countryMetadata = _getCountryMetadata(number.getCountry());
655		var nationalPrefix = _getNationalPrefix(countryMetadata, true);
656		var hasNationalPrefix = _numberContainsNationalPrefix(number.getRawNumber(), nationalPrefix, countryMetadata);
657
658		if(!isInternational && hasNationalPrefix)
659		{
660			var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata);
661			if(nationalPrefixFormattingRule != '')
662			{
663				nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', nationalPrefix).replace('$FG', '$1');
664				replaceFormat = replaceFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule);
665			}
666			else
667			{
668				replaceFormat = nationalPrefix + ' ' + replaceFormat;
669			}
670		}
671
672		return nationalNumber.replace(patternRegex, replaceFormat);
673	};
674
675	BX.PhoneNumberFormatter.getNationalPrefixFormattingRule = function (countryMetadata, format)
676	{
677		var result = _getNationalPrefixFormattingRule(format, countryMetadata);
678
679		return result.replace('$NP', countryMetadata['nationalPrefix']).replace('$FG', '$1');
680	};
681
682	BX.PhoneNumberFormatter.getNationalPrefixOptional = function(countryMetadata, format)
683	{
684		if(BX.type.isPlainObject(format) && format.hasOwnProperty('nationalPrefixOptionalWhenFormatting'))
685			return format['nationalPrefixOptionalWhenFormatting'];
686		else if(countryMetadata.hasOwnProperty('nationalPrefixOptionalWhenFormatting'))
687			return countryMetadata['nationalPrefixOptionalWhenFormatting'];
688		else
689			return false;
690	};
691
692
693	/* Partial number formatter (used in phone input control */
694
695	var DUMMY_DIGIT = '9';
696	var DUMMY_DIGIT_MATCHER = new RegExp(DUMMY_DIGIT, 'g');
697	var LONGEST_NATIONAL_PHONE_NUMBER_LENGTH = 15;
698	var LONGEST_DUMMY_PHONE_NUMBER = _repeat(DUMMY_DIGIT, LONGEST_NATIONAL_PHONE_NUMBER_LENGTH);
699	var DIGIT_PLACEHOLDER = 'x';
700	var DIGIT_PLACEHOLDER_MATCHER = new RegExp(DIGIT_PLACEHOLDER);
701	var DIGIT_PLACEHOLDER_MATCHER_GLOBAL = new RegExp(DIGIT_PLACEHOLDER, 'g');
702	var CHARACTER_CLASS_PATTERN = new RegExp('\\[([^\\[\\]])*\\]', 'g'); //matches character class in some other regexp
703
704	// Any digit in a regular expression that actually denotes a digit. For
705	// example, in the regular expression "80[0-2]\d{6,10}", the first 2 digits
706	// (8 and 0) are standalone digits, but the rest are not.
707	// Two look-aheads are needed because the number following \\d could be a
708	// two-digit number, since the phone number can be as long as 15 digits.
709	var STANDALONE_DIGIT_PATTERN = new RegExp('\\d(?=[^,}][^,}])', 'g');
710
711	// A pattern that is used to determine if a `format` is eligible
712	// to be used by the "as you type formatter".
713	// It is eligible when the `format` contains groups of the dollar sign
714	// followed by a single digit, separated by valid phone number punctuation.
715	// This prevents invalid punctuation (such as the star sign in Israeli star numbers)
716	// getting into the output of the "as you type formatter".
717	var ELIGIBLE_FORMAT_MATCHER = new RegExp('^' + '[' + validPunctuation + ']*' + '(\\$\\d[' + validPunctuation + ']*)+' + '$');
718
719	// This is the minimum length of the leading digits of a phone number
720	// to guarantee the first "leading digits pattern" for a phone number format
721	// to be preemptive.
722	var MIN_LEADING_DIGITS_LENGTH = 3;
723
724	var VALID_INCOMPLETE_PHONE_NUMBER = '[' + plusChar + ']{0,1}' + '[' + validPunctuation + validDigits + ']*';
725	var VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i');
726
727	BX.PhoneNumber.IncompleteFormatter = function(defaultCountry)
728	{
729		if(!metadataLoaded)
730		{
731			throw new Error("Metadata is not loaded yet. Do not construct this class directly, use BX.PhoneNumber.getIncompleteFormatter instead");
732		}
733
734		this.defaultCountry = defaultCountry || BX.PhoneNumber.getDefaultCountry();
735
736		this.rawInput = '';
737
738		this.country = '';
739		this.countryCode = '';
740		this.countryMetadata = null;
741		this.nationalPrefix = '';
742		this.nationalNumber = '';
743		this.isInternational = false;
744		this.hasNationalPrefix = false;
745		this.hasPlusChar = false;
746		this.formattedNumber = null;
747		this.extension = '';
748		this.extensionSeparator = '';
749	};
750
751	BX.PhoneNumber.IncompleteFormatter.prototype.format = function(incompleteNumber)
752	{
753		this.resetState();
754
755		var extractedNumber = _extractFormattedPhoneNumber(incompleteNumber);
756
757		if(!extractedNumber && incompleteNumber[0] === plusChar)
758		{
759			this.rawInput = incompleteNumber;
760			this.formattedNumber = incompleteNumber;
761			return incompleteNumber;
762		}
763
764		this.isInternational = extractedNumber[0] === plusChar;
765
766		var stripResult = _stripExtension(extractedNumber);
767		extractedNumber = stripResult.phoneNumber;
768		this.extension = stripResult.extension;
769		this.extensionSeparator = stripResult.extensionSeparator;
770
771		extractedNumber = _stripLetters(extractedNumber);
772		this.rawInput = extractedNumber;
773		if(this.isInternational)
774		{
775			this.hasPlusChar = true;
776			this.rawInput = plusChar + extractedNumber;
777		}
778
779		if(this.isInternational)
780		{
781			this.extractCountryCode();
782			if(!this.countryCode)
783			{
784				return this.rawInput;
785			}
786
787			this.findSuitableCountry();
788		}
789		else if(!this.defaultCountry)
790		{
791			return this.rawInput;
792		}
793		else
794		{
795			this.country = this.defaultCountry;
796			this.countryMetadata = _getCountryMetadata(this.country);
797			if(!this.countryMetadata)
798			{
799				return this.rawInput;
800			}
801			this.nationalNumber = this.rawInput;
802			this.extractNationalPrefix();
803
804			if(!this.hasNationalPrefix)
805			{
806				this.tryToStripCountryCode();
807			}
808		}
809
810		return this.getFormattedNumber();
811	};
812
813	BX.PhoneNumber.IncompleteFormatter.prototype.getFormattedNumber = function()
814	{
815		var formattedNationalNumber = this.formatNationalNumber();
816		var result = formattedNationalNumber ? formattedNationalNumber : this.rawInput;
817
818		if(this.extensionSeparator)
819		{
820			result += this.extensionSeparator + " " + this.extension;
821		}
822
823		return result;
824	};
825
826	BX.PhoneNumber.IncompleteFormatter.prototype.extractCountryCode = function()
827	{
828		var parseResult = _parsePhoneNumberAndCountryPhoneCode(this.rawInput);
829		if(parseResult && parseResult['countryCode'])
830		{
831			this.countryCode = parseResult['countryCode'];
832			this.nationalNumber = parseResult['localNumber'];
833		}
834	};
835
836	BX.PhoneNumber.IncompleteFormatter.prototype.tryToStripCountryCode = function()
837	{
838		var possibleCountryCode = this.countryMetadata['countryCode'];
839		var possibleNationalNumber;
840		if(this.nationalNumber.indexOf(possibleCountryCode) === 0)
841		{
842			possibleNationalNumber = this.nationalNumber.substr(possibleCountryCode.length);
843			if(_isNumberPossible(possibleNationalNumber, this.countryMetadata, true, false))
844			{
845				this.isInternational = true;
846				this.countryCode = possibleCountryCode;
847				this.nationalNumber = possibleNationalNumber;
848			}
849		}
850	};
851
852	BX.PhoneNumber.IncompleteFormatter.prototype.extractNationalPrefix = function()
853	{
854		var possibleNationalNumber = _stripNationalPrefix(this.nationalNumber, this.countryMetadata);
855
856		if(possibleNationalNumber !== this.nationalNumber)
857		{
858			if(!_isNumberPossible(possibleNationalNumber, this.countryMetadata, false, true))
859			{
860				return false;
861			}
862			this.hasNationalPrefix = true;
863			//this.nationalPrefix = this.nationalNumber.substr(0, this.nationalNumber.length - possibleNationalNumber.length);
864			this.nationalPrefix = this.countryMetadata['nationalPrefix'];
865			this.nationalNumber = possibleNationalNumber;
866			return true;
867		}
868		return false;
869	};
870
871	BX.PhoneNumber.IncompleteFormatter.prototype.resetState = function()
872	{
873		this.country = null;
874		this.countryCode = '';
875		this.nationalPrefix = '';
876		this.nationalNumber = null;
877		this.isInternational = false;
878		this.hasNationalPrefix = false;
879		this.hasPlusChar = false;
880		this.selectedFormat = null;
881		this.formattedNumber = null;
882		this.formattingTemplate = null;
883		this.extension = '';
884		this.extensionSeparator = '';
885	};
886
887	BX.PhoneNumber.IncompleteFormatter.prototype.findSuitableCountry = function()
888	{
889		var possibleCountry = _findCountry(this.countryCode, this.nationalNumber);
890
891		if(possibleCountry)
892			this.country = possibleCountry;
893		else
894			this.country = _getMainCountryForCode(this.countryCode);
895
896		this.countryMetadata = _getCountryMetadata(this.country);
897	};
898
899	BX.PhoneNumber.IncompleteFormatter.prototype.formatNationalNumber = function()
900	{
901		if(this.isCompleteNumber())
902		{
903			return this.formatCompleteNumber(this.nationalNumber);
904		}
905
906		if(!this.isInternational && this.countryCode === '' && this.nationalPrefix === '' && ShortNumberFormatter.isApplicable(this.rawInput))
907		{
908			return ShortNumberFormatter.format(this.rawInput);
909		}
910
911		if(this.selectFormat())
912		{
913			this.formattedNumber = this.formatUsingTemplate();
914
915			if(this.isInternational)
916			{
917				var formattedNumber = (this.hasPlusChar ? plusChar : '') + this.countryCode + ' ' + this.formattedNumber;
918			}
919			else
920			{
921				formattedNumber = this.formattedNumber;
922			}
923
924			// If no digit was inserted/removed/altered in a process of formatting, return the formatted number;
925			var normalizedFormattedNumber = _stripLetters(formattedNumber);
926			var normalizedRawInput = _stripLetters(this.rawInput);
927			if (normalizedFormattedNumber !== normalizedRawInput)
928			{
929				formattedNumber = this.rawInput;
930			}
931
932			return formattedNumber;
933		}
934	};
935
936	BX.PhoneNumber.IncompleteFormatter.prototype.isCompleteNumber = function()
937	{
938		return _getNumberType(this.nationalNumber, this.country) ? true : false;
939	};
940
941	/**
942	 * Take first format with suitable leading digits
943	 * @returns boolean
944	 */
945	BX.PhoneNumber.IncompleteFormatter.prototype.selectFormat = function()
946	{
947		var availableFormats = _getAvailableFormats(this.countryMetadata);
948
949		for (var i = 0; i < availableFormats.length; i++)
950		{
951			var format = availableFormats[i];
952
953			if(!this.isFormatSuitable(format))
954				continue;
955
956			if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(this.nationalNumber, format['leadingDigits']))
957				continue;
958
959			if(!this.createFormattingTemplate(format))
960				continue;
961
962			this.selectedFormat = format;
963			return true;
964
965		}
966		return false;
967
968	};
969
970	BX.PhoneNumber.IncompleteFormatter.prototype.createFormattingTemplate = function(format)
971	{
972		var pattern = format['pattern'];
973
974		// The IncompleteFormatter doesn't format numbers when pattern contains "|", e.g. (20|3)\d{4}.
975		if(pattern.indexOf('|') !== -1)
976			return false;
977
978		this.formattingTemplate = "";
979		var possibleTemplate = this.getFormattingTemplate(pattern, format);
980		if(possibleTemplate)
981		{
982			this.formattingTemplate = possibleTemplate;
983			return true;
984		}
985		return false;
986	};
987
988	BX.PhoneNumber.IncompleteFormatter.prototype.getFormattingTemplate = function(numberPattern, format)
989	{
990		var numberFormat = _getFormatFormat(format, this.isInternational);
991
992		// replace anything in the form of [..] with \d
993		var modifiedPattern = numberPattern.replace(CHARACTER_CLASS_PATTERN, "\\d");
994
995		// Replace any standalone digit (not the one in d{}) with \d
996		modifiedPattern = modifiedPattern.replace(STANDALONE_DIGIT_PATTERN, "\\d");
997
998		var longestNumberForPattern = LONGEST_DUMMY_PHONE_NUMBER.match(new RegExp(modifiedPattern))[0];
999
1000		// formatting template can not be applied right now if current number length > longest number length
1001
1002		if(this.nationalNumber.length > longestNumberForPattern.length)
1003			return false;
1004
1005		if(this.hasNationalPrefix)
1006		{
1007			var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, this.countryMetadata);
1008			if(nationalPrefixFormattingRule)
1009			{
1010				nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', this.nationalPrefix).replace('$FG', '$1');
1011				numberFormat = numberFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule);
1012			}
1013			else
1014			{
1015				numberFormat = this.nationalPrefix + ' ' + numberFormat;
1016			}
1017		}
1018
1019		// format the longest number according to the numberFormat
1020		var template = longestNumberForPattern.replace(new RegExp(modifiedPattern, 'g'), numberFormat);
1021		// replace each digit with the placeholder
1022		template = template.replace(DUMMY_DIGIT_MATCHER, DIGIT_PLACEHOLDER);
1023		return template;
1024	};
1025
1026	BX.PhoneNumber.IncompleteFormatter.prototype.formatUsingTemplate = function()
1027	{
1028		if(!this.formattingTemplate)
1029			return false;
1030
1031		var result = this.formattingTemplate;
1032		var lastMatchPosition;
1033
1034		for(var i = 0; i< this.nationalNumber.length; i++)
1035		{
1036			lastMatchPosition = result.search(DIGIT_PLACEHOLDER_MATCHER);
1037			if(lastMatchPosition === -1)
1038				return false;
1039
1040			result = result.replace(DIGIT_PLACEHOLDER_MATCHER, this.nationalNumber[i]);
1041		}
1042
1043		result = this.closeLastBracket(result, lastMatchPosition + 1);
1044		return result;
1045	};
1046
1047	BX.PhoneNumber.IncompleteFormatter.prototype.closeLastBracket = function(partiallyPopulatedTemplate, cutAfter)
1048	{
1049		var remainingTemplatePart = partiallyPopulatedTemplate.substr(cutAfter);
1050
1051		var openingBracketPosition = remainingTemplatePart.indexOf('(');
1052		var closingBracketPosition = remainingTemplatePart.indexOf(')');
1053
1054		if(closingBracketPosition !== -1 && (openingBracketPosition === -1 || openingBracketPosition > closingBracketPosition))
1055		{
1056			cutAfter = cutAfter + closingBracketPosition + 1;
1057		}
1058
1059
1060		return partiallyPopulatedTemplate.substr(0, cutAfter).replace(DIGIT_PLACEHOLDER_MATCHER_GLOBAL, ' ');
1061	};
1062
1063	 BX.PhoneNumber.IncompleteFormatter.prototype.formatCompleteNumber = function()
1064	{
1065		var phoneNumber = new BX.PhoneNumber();
1066		phoneNumber.setRawNumber(this.rawInput);
1067		phoneNumber.setHasPlus(this.hasPlusChar);
1068		phoneNumber.setInternational(this.isInternational);
1069		phoneNumber.setNationalPrefix(this.nationalPrefix);
1070		phoneNumber.setNationalNumber(this.nationalNumber);
1071		phoneNumber.setCountry(this.country);
1072		phoneNumber.setCountryCode(this.countryCode);
1073
1074		var format = BX.PhoneNumberFormatter.selectOriginalFormatForNumber(phoneNumber);
1075
1076		if(!format)
1077			return false;
1078
1079		var formattedNumber = BX.PhoneNumberFormatter.formatNationalNumberWithOriginalFormat(phoneNumber, format);
1080
1081		if(this.isInternational)
1082		{
1083			formattedNumber = (this.hasPlusChar ? plusChar : '') + this.countryCode + ' ' + formattedNumber;
1084		}
1085
1086		this.selectedFormat = format;
1087		return formattedNumber;
1088	};
1089
1090	BX.PhoneNumber.IncompleteFormatter.prototype.isFormatSuitable = function(format)
1091	{
1092		if(this.isInternational)
1093		{
1094			return _getInternationalFormat(format) ? true : false;
1095		}
1096		else
1097		{
1098			return !this.hasNationalPrefix || _isNationalPrefixSupported(format, this.countryMetadata);
1099		}
1100	};
1101
1102	BX.PhoneNumber.IncompleteFormatter.prototype.replaceCountry = function (country)
1103	{
1104		this.isInternational = true;
1105		this.hasPlusChar = true;
1106		this.country = country;
1107		this.countryMetadata = _getCountryMetadata(this.country);
1108		this.countryCode = this.countryMetadata['countryCode'];
1109		this.rawInput = '+' + this.countryCode + this.nationalNumber;
1110		this.nationalPrefix = '';
1111	};
1112
1113	/**
1114	 *
1115	 * @param {object} params
1116	 * @param {Element} params.node
1117	 * @param {Element} [params.flagNode]
1118	 * @param {int} [params.flagSize] 16, 24 or 32
1119	 * @param {string} [params.defaultCountry]
1120	 * @param {string} [params.userDefaultCountry]
1121	 * @param {string} [params.savedCountryCode]
1122	 * @param {int} [params.countryPopupHeight] 180
1123	 * @param {string} [params.countryPopupClassName] ''
1124	 * @param {Array} [params.countryTopList]
1125	 * @param {boolean} [params.forceLeadingPlus]
1126	 * @param {Function} [params.onInitialize]
1127	 * @param {Function} [params.onChange]
1128	 * @param {Function} [params.onCountryChange]
1129	 * @constructor
1130	 */
1131	BX.PhoneNumber.Input = function(params)
1132	{
1133		if(!BX.type.isDomNode(params.node) || params.node.nodeName !== 'INPUT' || params.node.type !== 'text')
1134		{
1135			throw new Error("params.node should be text input node");
1136		}
1137
1138		this.inputNode = params.node;
1139		this.defaultCountry = params.defaultCountry || BX.PhoneNumber.getDefaultCountry();
1140		this.userDefaultCountry = params.userDefaultCountry || BX.PhoneNumber.getUserDefaultCountry();
1141		this.savedCountryCode = params.savedCountryCode || '';
1142		this.forceLeadingPlus = params.forceLeadingPlus === true;
1143		this.flagNode = BX.type.isDomNode(params.flagNode) ? params.flagNode : null;
1144		this.flagSize = ([16, 24, 32].indexOf(params.flagSize) !== -1) ? params.flagSize : 16;
1145		this.countryPopupHeight = params.countryPopupHeight || 180;
1146		this.countryPopupClassName = params.countryPopupClassName || '';
1147		this.countryTopList = params.countryTopList || [];
1148		this.flagNodeInitialClass = '';
1149
1150		this.countries = null;
1151
1152		this.callbacks = {
1153			initialize: BX.type.isFunction(params.onInitialize) ? params.onInitialize : BX.DoNothing,
1154			change: BX.type.isFunction(params.onChange) ? params.onChange : BX.DoNothing,
1155			countryChange: BX.type.isFunction(params.onCountryChange) ? params.onCountryChange : BX.DoNothing
1156		};
1157
1158		this.formatter = null;
1159		this.countrySelectPopup = null;
1160
1161		this._lastCaretPosition = null;
1162		this._digitsToTheLeft = 0;
1163		this._digitsToTheRight = 0;
1164		this._digitsCount = 0;
1165		this._selectedDigitsBeforeAction = 0;
1166		this._countryBefore = '';
1167
1168		this.initialized = false;
1169		this.initializationPromises = [];
1170
1171		this.init();
1172		this.bindEvents();
1173	};
1174
1175	BX.PhoneNumber.Input.prototype.init = function()
1176	{
1177		var self = this;
1178
1179		if(this.flagNode)
1180		{
1181			this.flagNodeInitialClass = this.flagNode.className;
1182			BX.adjust(this.flagNode, {style: {
1183				cursor: "pointer",
1184				display: "inline-block"
1185			}});
1186		}
1187
1188		BX.PhoneNumber.getIncompleteFormatter(this.defaultCountry).then(function(formatter)
1189		{
1190			self.formatter = formatter;
1191
1192			if(self.inputNode.value)
1193			{
1194				self.inputNode.value = self.formatter.format(self.inputNode.value);
1195
1196				if (
1197					BX.Type.isStringFilled(self.savedCountryCode)
1198					&& self.formatter.country !== self.savedCountryCode
1199				)
1200				{
1201					self.formatter.replaceCountry(self.savedCountryCode);
1202				}
1203			}
1204			else if(self.userDefaultCountry != '')
1205			{
1206				self.formatter.replaceCountry(self.userDefaultCountry);
1207				self.inputNode.value = self.userDefaultCountry === 'XX' ? '' : self.formatter.getFormattedNumber();
1208			}
1209			self.drawCountryFlag();
1210			self.initialized = true;
1211			self.initializationPromises.forEach(function(promise)
1212			{
1213				promise.resolve();
1214			});
1215			self.callbacks.initialize();
1216		});
1217	};
1218
1219	BX.PhoneNumber.Input.prototype.bindEvents = function ()
1220	{
1221		this.inputNode.addEventListener('keydown', this._onKeyDown.bind(this));
1222		this.inputNode.addEventListener('input', this._onInput.bind(this));
1223		if(this.flagNode)
1224		{
1225			this.flagNode.addEventListener('click', this._onFlagClick.bind(this));
1226		}
1227	};
1228
1229	BX.PhoneNumber.Input.prototype.setValue = function (newValue)
1230	{
1231		this.waitForInitialization().then(function()
1232		{
1233			this.inputNode.value = this.formatter.format(newValue.toString());
1234			this.callbacks.change({
1235				value: this.getValue(),
1236				formattedValue: this.getFormattedValue(),
1237				country: this.getCountry(),
1238				countryCode: this.getCountryCode()
1239			});
1240
1241			if(this._countryBefore !== this.getCountry())
1242			{
1243				this.drawCountryFlag();
1244				this.callbacks.countryChange({
1245					country: this.getCountry(),
1246					countryCode: this.getCountryCode()
1247				});
1248			}
1249		}.bind(this));
1250	};
1251
1252	BX.PhoneNumber.Input.prototype.waitForInitialization = function()
1253	{
1254		var result = new BX.Promise();
1255
1256		if(this.initialized)
1257		{
1258			result.resolve();
1259			return result;
1260		}
1261
1262		this.initializationPromises.push(result);
1263		return result;
1264	};
1265
1266	BX.PhoneNumber.Input.prototype.getValue = function()
1267	{
1268		return _stripNonSignificantChars(this.inputNode.value);
1269	};
1270
1271	BX.PhoneNumber.Input.prototype.getFormattedValue = function()
1272	{
1273		return this.inputNode.value;
1274	};
1275
1276	BX.PhoneNumber.Input.prototype.getCountry = function()
1277	{
1278		return this.formatter.country || this.formatter.defaultCountry;
1279	};
1280
1281	BX.PhoneNumber.Input.prototype.getCountryCode = function()
1282	{
1283		var countryMetadata = _getCountryMetadata(this.getCountry());
1284		return (countryMetadata ? countryMetadata['countryCode'] : false);
1285	};
1286
1287	BX.PhoneNumber.Input.prototype.drawCountryFlag = function ()
1288	{
1289		if (!this.flagNode)
1290			return;
1291
1292		var country = this.getCountry();
1293		if (!BX.type.isNotEmptyString(country))
1294			return;
1295
1296		country = country.toLowerCase();
1297		BX.adjust(this.flagNode, {props: {className: this.flagNodeInitialClass + " bx-flag-" + this.flagSize + " " + country}});
1298	};
1299
1300	BX.PhoneNumber.Input.prototype.tryRedrawCountryFlag = function()
1301	{
1302		if (this._countryBefore !== this.getCountry())
1303		{
1304			this.drawCountryFlag();
1305
1306			this.callbacks.countryChange({
1307				country: this.getCountry(),
1308				countryCode: this.getCountryCode()
1309			});
1310		}
1311	};
1312
1313	BX.PhoneNumber.Input.prototype._onKeyDown = function (e)
1314	{
1315		if(!e.key)
1316			return;
1317		var selectedCount = this.inputNode.selectionEnd - this.inputNode.selectionStart;
1318		//skip letters
1319		if(e.key === plusChar)
1320		{
1321			//allow + char only in the beginning
1322			if(this.inputNode.selectionStart !== 0)
1323			{
1324				e.preventDefault();
1325				e.stopPropagation();
1326				return;
1327			}
1328		}
1329		else if(e.key.length === 1 && e.key.search(/[\d,;#]/) !== 0 && !e.ctrlKey && !e.metaKey)
1330		{
1331			e.preventDefault();
1332			e.stopPropagation();
1333			return;
1334		}
1335
1336		var digitsPositions = _getDigitPositions(this.inputNode.value);
1337
1338		//remember cursor position before keyboard input
1339		this._lastCaretPosition = this.inputNode.selectionStart;
1340		this._digitsToTheLeft = _countMatches(significantChars, this.inputNode.value.substr(0, this._lastCaretPosition));
1341		this._digitsToTheRight = _countMatches(significantChars, this.inputNode.value.substr(this._lastCaretPosition));
1342		this._digitsCount = _countMatches(significantChars, this.inputNode.value);
1343		this._countryBefore = this.getCountry();
1344
1345		if(selectedCount > 0)
1346		{
1347			var selectedFragment = this.inputNode.value.substr(this.inputNode.selectionStart, selectedCount);
1348			this._selectedDigitsBeforeAction = _countMatches(significantChars, selectedFragment);
1349		}
1350		else
1351		{
1352			this._selectedDigitsBeforeAction = 0;
1353		}
1354
1355		//move cursor so it would delete digit instead of formatting
1356		var newCaretPosition = null;
1357		if(e.key === 'Backspace' && selectedCount === 0)
1358		{
1359			newCaretPosition = digitsPositions[this._digitsToTheLeft - 1] + 1;
1360		}
1361
1362		if(e.key === 'Delete' && selectedCount === 0 && this._digitsToTheRight > 0)
1363		{
1364			newCaretPosition = digitsPositions[this._digitsToTheLeft];
1365		}
1366
1367		if(newCaretPosition !== null)
1368		{
1369			this.inputNode.setSelectionRange(newCaretPosition, newCaretPosition);
1370		}
1371	};
1372
1373	BX.PhoneNumber.Input.prototype._onInput = function(e)
1374	{
1375		var caretPosition = null;
1376
1377		if(this.formatter)
1378		{
1379			var formattedValue = this.formatter.format(this.inputNode.value);
1380			var digitsPositions = _getDigitPositions(formattedValue);
1381			var digitsBefore = this._digitsCount;
1382			var digitsDeleted = this._selectedDigitsBeforeAction;
1383			var digitsAfter = _countMatches(significantChars, formattedValue);
1384			var digitsDelta = digitsAfter - digitsBefore;
1385			var digitsInserted = digitsDelta + digitsDeleted;
1386
1387			// restore caret position
1388			if(this._lastCaretPosition !== null)
1389			{
1390				switch (e.inputType)
1391				{
1392					case 'deleteContentBackward':
1393						//backspace is pressed, need to move cursor one position left if one symbol was deleted
1394						if(digitsDelta == -1)
1395							caretPosition = digitsPositions[this._digitsToTheLeft + digitsDelta - 1] + 1;
1396						else
1397							caretPosition = digitsPositions[this._digitsToTheLeft];
1398						break;
1399					case 'deleteContentForward':
1400						//delete is pressed, don't move cursor
1401						if(this._digitsToTheLeft === 0)
1402						{
1403							caretPosition = digitsPositions[0];
1404						}
1405						else
1406						{
1407							caretPosition = digitsPositions[this._digitsToTheLeft - 1] + 1;
1408						}
1409						break;
1410					case 'insertText':
1411					case 'insertFromPaste':
1412						//move caret after inserted digits
1413						caretPosition = digitsPositions[this._digitsToTheLeft - 1 + digitsInserted] + 1;
1414
1415						break;
1416				}
1417			}
1418
1419			this.inputNode.value = formattedValue;
1420			if(caretPosition !== null)
1421			{
1422				this.inputNode.setSelectionRange(caretPosition, caretPosition);
1423			}
1424
1425			this.callbacks.change({
1426				value: this.getValue(),
1427				formattedValue: this.getFormattedValue(),
1428				country: this.getCountry(),
1429				countryCode: this.getCountryCode()
1430			});
1431
1432			this.tryRedrawCountryFlag();
1433		}
1434		this._lastCaretPosition = null;
1435	};
1436
1437	BX.PhoneNumber.Input.prototype._onFlagClick = function (e)
1438	{
1439		/*if(this.formatter.nationalNumber != '')
1440			return;*/
1441
1442		this.selectCountry({
1443			node: this.flagNode,
1444			countryPopupHeight: this.countryPopupHeight,
1445			countryPopupClassName: this.countryPopupClassName,
1446			countryTopList: this.countryTopList,
1447			onSelect: this._onCountrySelect.bind(this)
1448		});
1449	};
1450
1451	BX.PhoneNumber.Input.prototype._onCountrySelect = function(e)
1452	{
1453		var country = e.country;
1454		if(country === this.getCountry())
1455			return false;
1456
1457		this.formatter.replaceCountry(country);
1458		this.inputNode.value = this.formatter.getFormattedNumber();
1459		this.drawCountryFlag();
1460		this.callbacks.change({
1461			value: this.getValue(),
1462			formattedValue: this.getFormattedValue(),
1463			country: this.getCountry(),
1464			countryCode: this.getCountryCode()
1465		});
1466		this.callbacks.countryChange({
1467			country: this.getCountry(),
1468			countryCode: this.getCountryCode()
1469		});
1470		BX.userOptions.save('main', 'phone_number', 'default_country', country);
1471	};
1472
1473	BX.PhoneNumber.Input.prototype.loadCountries = function()
1474	{
1475		var result = new BX.Promise();
1476		if(this.countries)
1477		{
1478			result.fulfill();
1479			return result;
1480		}
1481
1482		BX.ajax.runAction("main.phonenumber.getCountries").then(function(response)
1483		{
1484			this.countries = response.data;
1485			result.fulfill();
1486		}.bind(this)).catch(function(response)
1487		{
1488			if(response.errors)
1489			{
1490				response.errors.map(function(error)
1491				{
1492					console.error(error.message);
1493				});
1494			}
1495			else
1496			{
1497				console.error(response);
1498			}
1499		});
1500		return result;
1501	};
1502
1503	BX.PhoneNumber.Input.prototype.selectCountry = function (params)
1504	{
1505		var self = this;
1506		var onSelect  = BX.type.isFunction(params.onSelect) ? params.onSelect : BX.DoNothing;
1507		var popupContent = BX.create("span", {
1508			events: {
1509				click: BX.delegateEvent(
1510					{
1511						attribute: 'data-country',
1512					},
1513					function()
1514					{
1515						self.countrySelectPopup.close();
1516						onSelect({
1517							country: this.getAttribute('data-country')
1518						});
1519					}
1520				)
1521			}
1522		});
1523
1524		var separator = null;
1525		var topList = {};
1526		if(params.countryTopList && params.countryTopList.length > 0)
1527		{
1528			separator = popupContent.appendChild(
1529				BX.create('span', {props: {className: 'main-phonenumber-country-separator'}})
1530			);
1531		}
1532
1533		this.loadCountries().then(function()
1534		{
1535			self.countries.forEach(function(countryDescriptor)
1536			{
1537				var country = countryDescriptor.CODE;
1538				var countryCode = _getCountryCode(country);
1539
1540				if(!countryCode)
1541					return;
1542
1543
1544				var countryNode = popupContent.appendChild(BX.create("div", {
1545					props: {className: "main-phonenumber-country"},
1546					attrs: {'data-country': countryDescriptor.CODE},
1547					children: [
1548						BX.create("span", {
1549							props: {className: "main-phonenumber-country-flag bx-flag-16 " + country.toLowerCase()}
1550						}),
1551						BX.create("span", {
1552							props: {className: "main-phonenumber-country-name"},
1553							text: countryDescriptor.NAME + ' (+' + countryCode + ')'
1554						})
1555					]
1556				}));
1557
1558				if(params.countryTopList.indexOf(countryDescriptor.CODE) >= 0)
1559				{
1560					topList[countryDescriptor.CODE] = countryNode.cloneNode(true);
1561				}
1562			});
1563
1564			if(params.countryTopList && params.countryTopList.length > 0)
1565			{
1566				params.countryTopList.forEach(function(countryCode)
1567				{
1568					if(typeof topList[countryCode] !== 'undefined')
1569					{
1570						popupContent.insertBefore(topList[countryCode], separator);
1571					}
1572				});
1573
1574				if (popupContent.firstChild === separator)
1575				{
1576					popupContent.removeChild(separator);
1577				}
1578			}
1579
1580			self.countrySelectPopup = new BX.PopupWindow(
1581				'phoneNumberInputSelectCountry',
1582				params.node,
1583				{
1584					className: params.countryPopupClassName || '',
1585					autoHide: true,
1586					closeByEsc: true,
1587					bindOptions: {
1588						position: 'top'
1589					},
1590					height: params.countryPopupHeight,
1591					offsetRight: 35,
1592					padding: 0,
1593					contentPadding: 10,
1594					angle: {
1595						offset: 33
1596					},
1597					overlay: {
1598						backgroundColor: 'white',
1599						opacity: 0
1600					},
1601					content: popupContent,
1602					events: {
1603						onPopupClose : function()
1604						{
1605							self.countrySelectPopup.destroy();
1606						},
1607						onPopupDestroy: function ()
1608						{
1609							self.countrySelectPopup = null;
1610						}
1611					}
1612				}
1613			);
1614			self.countrySelectPopup.show();
1615		});
1616	};
1617
1618// Internal functions
1619
1620	var ShortNumberFormatter = {
1621		templates: {
1622			3: 'x-xx',
1623			4: 'xx-xx',
1624			5: 'x-xx-xx',
1625			6: 'xx-xx-xx',
1626			7: 'xxx-xx-xx'
1627		},
1628
1629		/**
1630		 *
1631		 * @param {string} rawNumber
1632		 * @return string
1633		 */
1634		format: function(rawNumber)
1635		{
1636			var template = this.templates[rawNumber.length];
1637			if(!template)
1638			{
1639				return rawNumber;
1640			}
1641
1642			var i = 0;
1643			var pattern = new RegExp(template.replace(/[^x]/g, "").replace(/x/g, "(\\d)"));
1644			var format = template.replace(/x/g, function (){ return "$" + ++i;});
1645
1646			return rawNumber.replace(pattern, format);
1647		},
1648
1649		/**
1650		 * Return true if the phone number could be formatted using this formatter and false otherwise.
1651		 * @param {string} rawNumber
1652		 * @return boolean
1653		 */
1654		isApplicable: function(rawNumber)
1655		{
1656			return /^\d{3,7}$/.test(rawNumber);
1657		}
1658	};
1659
1660	/**
1661	 * Extracts phone number from the input string.
1662	 * @param {string} phoneNumber Phone number.
1663	 * @return string
1664	 */
1665	var _extractFormattedPhoneNumber = function(phoneNumber)
1666	{
1667		if (!phoneNumber || phoneNumber.length > MAX_INPUT_STRING_LENGTH)
1668		{
1669			return '';
1670		}
1671
1672		var startsAt = phoneNumber.search(new RegExp(phoneNumberStartPattern));
1673
1674		// Attempt to extract a possible number from the string passed in
1675		if (startsAt < 0)
1676		{
1677			return '';
1678		}
1679
1680		var result = phoneNumber.substr(startsAt);
1681		result = result.replace(new RegExp(afterPhoneNumberEndPattern), '');
1682		return result;
1683	};
1684
1685	/**
1686	 * Returns country code and local number for the provided international phone number.
1687	 * @param {string} phoneNumber Phone number in international format.
1688	 * @return object|boolean
1689	 */
1690	var _parsePhoneNumberAndCountryPhoneCode = function(phoneNumber)
1691	{
1692		phoneNumber = _stripNonSignificantChars(phoneNumber);
1693		if(!phoneNumber)
1694			return false;
1695
1696		// If this is not an international phone number,
1697		// then don't extract country phone code.
1698		if (phoneNumber[0] !== plusChar)
1699		{
1700			return {
1701				'countryCode': '',
1702				'localNumber': phoneNumber
1703			};
1704		}
1705
1706		// Strip the leading '+' sign
1707		phoneNumber = phoneNumber.substr(1);
1708
1709		// Fast abortion: country codes do not begin with a '0'
1710		if (phoneNumber[0] === '0')
1711		{
1712			return false;
1713		}
1714
1715		for (var i = MAX_LENGTH_COUNTRY_CODE; i > 0; i--)
1716		{
1717			var countryCode = phoneNumber.substr(0, i);
1718			if(_isValidCountryCode(countryCode))
1719			{
1720				return {
1721					'countryCode': countryCode,
1722					'localNumber': phoneNumber.substr(i)
1723				};
1724			}
1725		}
1726		return false;
1727	};
1728
1729	/**
1730	 * Returns true if the specified string matches general phone number pattern.
1731	 * @param {string} phoneNumber Phone number.
1732	 * @return boolean
1733	 */
1734	var _isViablePhoneNumber = function(phoneNumber)
1735	{
1736		return phoneNumber.length >= MIN_LENGTH_FOR_NSN && (phoneNumber.search(new RegExp(validPhoneNumberPattern)) !== -1);
1737	};
1738
1739	/**
1740	 *
1741	 * @param {string} phoneNumber
1742	 * @private
1743	 */
1744	var _stripExtension = function(phoneNumber)
1745	{
1746		var extension = "";
1747		var extensionSeparator = "";
1748		var separatorPosition = phoneNumber.search(new RegExp("[" + extensionSeparators + "]"));
1749
1750		if(separatorPosition >= 0)
1751		{
1752			extensionSeparator = phoneNumber[separatorPosition];
1753			extension = phoneNumber.substr(separatorPosition);
1754			phoneNumber = phoneNumber.substr(0, separatorPosition);
1755		}
1756
1757		return {
1758			extensionSeparator: extensionSeparator,
1759			extension: _stripEverythingElse(extension, extensionSymbols + validDigits),
1760			phoneNumber: phoneNumber
1761		};
1762	};
1763
1764	/**
1765	 * Returns metadata for the first country with specified countryCode.
1766	 * @param {string} countryCode Phone code of the country
1767	 * @return object | false
1768	 */
1769	var _getMetadataByCountryCode = function(countryCode)
1770	{
1771		if(!_isValidCountryCode(countryCode))
1772		{
1773			return false;
1774		}
1775
1776		var countries = _getCountriesByCode(countryCode);
1777		return _getCountryMetadata(countries[0]);
1778	};
1779
1780	/**
1781	 * Returns 2-symbol country code by localNumber.
1782	 * @param {string} countryCode Phone code of the country.
1783	 * @param {string} localNumber Local phone number.
1784	 * @return string|boolean
1785	 */
1786	var _findCountry = function(countryCode, localNumber)
1787	{
1788		if(!countryCode || !localNumber)
1789			return false;
1790
1791		var possibleCountries = _getCountriesByCode(countryCode);
1792		var possibleCountry;
1793		var countryMetadata;
1794		if(possibleCountries.length === 1)
1795		{
1796			return possibleCountries[0];
1797		}
1798
1799		for (var i = 0; i < possibleCountries.length; i++)
1800		{
1801			possibleCountry = possibleCountries[i];
1802			countryMetadata = _getCountryMetadata(possibleCountry);
1803
1804			// Check leading digits first
1805			if(countryMetadata.hasOwnProperty('leadingDigits'))
1806			{
1807				var leadingDigitsRegex = '^(' + countryMetadata['leadingDigits'] + ')';
1808				if(localNumber.match(new RegExp(leadingDigitsRegex)))
1809				{
1810					return possibleCountry;
1811				}
1812			}
1813			// Else perform full validation with all of those bulky fixed-line/mobile/etc regular expressions.
1814			else if(_getNumberType(localNumber, possibleCountry))
1815			{
1816				return possibleCountry;
1817			}
1818		}
1819
1820		return false;
1821	};
1822
1823	/**
1824	 * Returns type of the specified number.
1825	 * @param {string} localNumber Local phone number.
1826	 * @param {string} country 2-symbol country code.
1827	 * @return string|boolean
1828	 */
1829	var _getNumberType = function(localNumber, country)
1830	{
1831		// Check that the number is valid for this country
1832		var countryMetadata = _getCountryMetadata(country);
1833		var possibleType;
1834		if(!countryMetadata)
1835			return false;
1836
1837		if(!BX.type.isNotEmptyString(localNumber))
1838			return false;
1839
1840		if((countryMetadata['generalDesc'] && countryMetadata['generalDesc']['nationalNumberPattern']))
1841		{
1842			if(!localNumber.match(new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$')))
1843				return false;
1844		}
1845
1846		var possibleTypes = ['noInternationalDialling', 'areaCodeOptional', 'fixedLine', 'mobile', 'pager', 'tollFree', 'premiumRate', 'sharedCost', 'personalNumber', 'voip', 'uan', 'voicemail'];
1847		for(var i = 0; i < possibleTypes.length; i++)
1848		{
1849			possibleType = possibleTypes[i];
1850			if((countryMetadata[possibleType] && countryMetadata[possibleType]['nationalNumberPattern']))
1851			{
1852				// skip checking possible lengths for now
1853
1854				if(localNumber.match(new RegExp('^' + countryMetadata[possibleType]['nationalNumberPattern'] + '$')))
1855				{
1856					return possibleType;
1857				}
1858			}
1859		}
1860		return false;
1861	};
1862
1863	/**
1864	 * Strips national prefix from the specified phone number. Returns true if national prefix
1865	 * was stripped and false otherwise.
1866	 * @param {string} phoneNumber Local phone number.
1867	 * @param {object} countryMetadata Country metadata.
1868	 * @return string
1869	 */
1870	var _stripNationalPrefix = function(phoneNumber, countryMetadata)
1871	{
1872		var nationalPrefixForParsing = countryMetadata.hasOwnProperty('nationalPrefixForParsing') ? countryMetadata['nationalPrefixForParsing']: countryMetadata['nationalPrefix'];
1873
1874		if(phoneNumber == '' || nationalPrefixForParsing == '')
1875			return phoneNumber;
1876
1877		var nationalPrefixRegex = '^(?:' + nationalPrefixForParsing + ')';
1878		var nationalPrefixMatches = phoneNumber.match(new RegExp(nationalPrefixRegex));
1879		if(!nationalPrefixMatches)
1880		{
1881			//if national prefix is omitted, nothing to strip
1882			return phoneNumber;
1883		}
1884
1885		var nationalPrefixTransformRule = countryMetadata['nationalPrefixTransformRule'];
1886		var nationalSignificantNumber;
1887		if(nationalPrefixTransformRule && nationalPrefixMatches.length > 1)
1888		{
1889			nationalSignificantNumber = phoneNumber.replace(nationalPrefixRegex, nationalPrefixTransformRule);
1890		}
1891		else
1892		{
1893			// No transformation is required, just strip the prefix
1894			nationalSignificantNumber = phoneNumber.substr(nationalPrefixMatches[0].length);
1895		}
1896
1897		return nationalSignificantNumber;
1898	};
1899
1900	var _isNumberValid = function(phoneNumber, countryMetadata)
1901	{
1902		var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$');
1903		if(phoneNumber.match(nationalNumberRegex, phoneNumber))
1904			return true;
1905		else
1906			return false;
1907	};
1908
1909	/**
1910	 * Phone number is possible if there is at least one format, suitable for formatting this number.
1911	 * @param phoneNumber
1912	 * @param countryMetadata
1913	 * @param isInternational
1914	 * @param hasNationalPrefix
1915	 * @private
1916	 */
1917	var _isNumberPossible = function(phoneNumber, countryMetadata, isInternational, hasNationalPrefix)
1918	{
1919		if(!countryMetadata['availableFormats'])
1920			return true;
1921
1922		for(var i = 0; i < countryMetadata.availableFormats.length; i++)
1923		{
1924			var format = countryMetadata.availableFormats[i];
1925			if(isInternational && format['intlFormat'] === 'NA')
1926				continue;
1927
1928			if(hasNationalPrefix)
1929			{
1930				var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata);
1931				if(nationalPrefixFormattingRule && nationalPrefixFormattingRule.search(/\$NP/) === -1)
1932					continue;
1933			}
1934
1935			if(format['leadingDigits'] && !_matchLeadingDigits(phoneNumber, format['leadingDigits']))
1936				continue
1937
1938			return true;
1939		}
1940
1941		return false;
1942	};
1943
1944	/**
1945	 * Strips country code from the number. Returns true if country code was stripped or false otherwise.
1946	 * @param {string} phoneNumber Phone number.
1947	 * @param {object} countryMetadata Country metadata.
1948	 * @return string
1949	 */
1950	var _stripCountryCode = function(phoneNumber, countryMetadata)
1951	{
1952		var countryCode = countryMetadata['countryCode'];
1953		if(phoneNumber.search(countryCode) !== 0)
1954			return phoneNumber;
1955
1956		var possibleLocalNumber = phoneNumber.substr(countryCode.length);
1957		var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$');
1958
1959		if(phoneNumber.match(nationalNumberRegex) && !possibleLocalNumber.match(nationalNumberRegex))
1960		{
1961			/*
1962			 If the original number (before stripping national prefix) was viable, and the resultant number is not,
1963			 then prefer the original phone number. This is because for some countries (e.g. Russia) the same digit
1964			 could be both a national prefix and a leading digit of a valid national phone number, like `8` is the
1965			 national prefix for Russia and both `8 800 555 35 35` and `800 555 35 35` are valid numbers.
1966			 */
1967			return phoneNumber;
1968		}
1969
1970		return possibleLocalNumber;
1971	};
1972
1973	var _isValidCountryCode = function(countryCode)
1974	{
1975		countryCode = countryCode.toString();
1976		return codeToCountries.hasOwnProperty(countryCode);
1977	};
1978
1979	var _getCountriesByCode = function(countryCode)
1980	{
1981		countryCode = countryCode.toString();
1982		return codeToCountries.hasOwnProperty(countryCode) ? codeToCountries[countryCode] : [];
1983	};
1984
1985	var _getMainCountryForCode = function(countryCode)
1986	{
1987		countryCode = countryCode.toString();
1988		return codeToCountries.hasOwnProperty(countryCode) ? codeToCountries[countryCode][0] : false;
1989	};
1990
1991	var _getCountryMetadata = function(country)
1992	{
1993		country = country.toUpperCase();
1994		return metadata.hasOwnProperty(country) ? metadata[country] : false;
1995	};
1996
1997	var _getCountryCode = function(country)
1998	{
1999		country = country.toUpperCase();
2000		return metadata.hasOwnProperty(country) ? metadata[country]['countryCode'] : false;
2001	};
2002
2003	var _getInternationalFormat = function(format)
2004	{
2005		if(format.hasOwnProperty('intlFormat'))
2006		{
2007			if(format['intlFormat'] === 'NA')
2008				return false;
2009			else
2010				return format['intlFormat'];
2011		}
2012		return format['format'];
2013	};
2014
2015	var _getAvailableFormats = function(countryMetadata)
2016	{
2017		if(BX.type.isArray(countryMetadata['availableFormats']))
2018			return countryMetadata['availableFormats'];
2019
2020		var countryCode = countryMetadata['countryCode'];
2021		var countriesForCode = _getCountriesByCode(countryCode);
2022		var mainCountry = countriesForCode[0];
2023		var mainCountryMetadata = _getCountryMetadata(mainCountry);
2024		return BX.type.isArray(mainCountryMetadata['availableFormats']) ? mainCountryMetadata['availableFormats'] : [];
2025
2026	};
2027
2028	var _getNationalPrefix = function(countryMetadata, stripNonDigits)
2029	{
2030		if(!countryMetadata.hasOwnProperty('nationalPrefix'))
2031		{
2032			return '';
2033		}
2034
2035		var nationalPrefix = countryMetadata['nationalPrefix'];
2036		if (stripNonDigits)
2037		{
2038			nationalPrefix = _stripLetters(nationalPrefix);
2039		}
2040		return nationalPrefix;
2041	};
2042
2043	var _getNationalPrefixFormattingRule = function (format, countryMetadata)
2044	{
2045		if(format.hasOwnProperty('nationalPrefixFormattingRule'))
2046		{
2047			return format['nationalPrefixFormattingRule'];
2048		}
2049		else
2050		{
2051			var countryCode = countryMetadata['countryCode'];
2052			var countriesForCode = _getCountriesByCode(countryCode);
2053			var mainCountry = countriesForCode[0];
2054			var mainCountryMetadata = _getCountryMetadata(mainCountry);
2055
2056			return mainCountryMetadata['nationalPrefixFormattingRule'] || '';
2057		}
2058	};
2059
2060	var _numberContainsNationalPrefix = function(phoneNumber, nationalPrefix, countryMetadata)
2061	{
2062		if (phoneNumber.indexOf(nationalPrefix) === 0)
2063		{
2064			// Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
2065			// when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
2066			// check the validity of the number if the assumed national prefix is removed (777123 won't
2067			// be valid in Japan).
2068			var numberWithoutPrefix = phoneNumber.substr(nationalPrefix.length);
2069			return BX.PhoneNumberParser.getInstance()._realParse(numberWithoutPrefix, countryMetadata['id']).isValid();
2070		}
2071		else
2072		{
2073			return false;
2074		}
2075	};
2076
2077	var _isNationalPrefixOptional = function(format, countryMetadata)
2078	{
2079		if(format.hasOwnProperty('nationalPrefixOptionalWhenFormatting'))
2080			return format['nationalPrefixOptionalWhenFormatting'];
2081		else if(countryMetadata.hasOwnProperty('nationalPrefixOptionalWhenFormatting'))
2082			return countryMetadata['nationalPrefixOptionalWhenFormatting'];
2083		else
2084			return false;
2085	};
2086
2087	/**
2088	 * National prefix is supported by the format if:
2089	 * 1.    Format and country metadata do not have nationalPrefixFormattingRule.
2090	 * 2. OR Format or country metadata contains nationalPrefixFormattingRule and this formatting rule contains "$NP"
2091	 * @param format
2092	 * @param countryMetadata
2093	 * @returns {boolean}
2094	 * @private
2095	 */
2096	var _isNationalPrefixSupported = function(format, countryMetadata)
2097	{
2098		var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata);
2099
2100		return (!nationalPrefixFormattingRule || nationalPrefixFormattingRule.search(/\$NP/) !== -1);
2101	};
2102
2103	var _matchLeadingDigits = function(phoneNumber, leadingDigits)
2104	{
2105		var re;
2106		var matches;
2107		if(BX.type.isArray(leadingDigits))
2108		{
2109			for (var i = 0; i < leadingDigits.length; i++)
2110			{
2111				re = new RegExp('^(' + leadingDigits[i] + ')');
2112				matches = phoneNumber.match(re);
2113				if(matches)
2114				{
2115					return matches;
2116				}
2117			}
2118		}
2119		else
2120		{
2121			re = new RegExp('^(' + leadingDigits + ')');
2122			matches = phoneNumber.match(re);
2123			if(matches)
2124			{
2125				return matches;
2126			}
2127		}
2128		return false;
2129	};
2130
2131	var _getFormatFormat = function(format, international)
2132	{
2133		if(international && format.hasOwnProperty('intlFormat'))
2134			return format['intlFormat'];
2135		else
2136			return format['format'];
2137	};
2138
2139	/**
2140	 * Strips all letters from the string.
2141	 * @param {string} str Input string.
2142	 * @return string
2143	 */
2144	var _stripLetters = function(str)
2145	{
2146		return _stripEverythingElse(str, validDigits)
2147	};
2148
2149	var _stripNonSignificantChars = function(str)
2150	{
2151		return _stripEverythingElse(str, significantChars);
2152	};
2153
2154	var _stripEverythingElse = function(str, allowedSymbols)
2155	{
2156		return str.replace(new RegExp("[^" + allowedSymbols + "]", "g"), "");
2157	};
2158
2159	var _countMatches = function(needle, haystack)
2160	{
2161		var matches = haystack.match(needle instanceof RegExp ? needle : new RegExp("[" + needle + "]", 'g'));
2162		return matches ? matches.length : 0;
2163	};
2164
2165	var _getDigitPositions = function(str)
2166	{
2167		var re = new RegExp("[" + significantChars + "]", "g");
2168		var result = [];
2169		var match;
2170
2171		while((match = re.exec(str)) !== null)
2172		{
2173
2174			result.push(match.index)
2175		}
2176		return result;
2177	};
2178
2179	function _repeat(str, times)
2180	{
2181		var result = '';
2182
2183		if(times <= 0)
2184			return '';
2185
2186		for(var i = 0; i < times; i++) result += str;
2187		return result;
2188	}
2189})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.