You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					191 lines
				
				6.0 KiB
			
		
		
			
		
	
	
					191 lines
				
				6.0 KiB
			| 
											3 years ago
										 | // This is a legacy function.
 | ||
|  | // Use `findNumbers()` instead.
 | ||
|  | 
 | ||
|  | import { | ||
|  | 	PLUS_CHARS, | ||
|  | 	VALID_PUNCTUATION, | ||
|  | 	VALID_DIGITS, | ||
|  | 	WHITESPACE | ||
|  | } from './constants.js' | ||
|  | 
 | ||
|  | import parse from './parse_.js' | ||
|  | import { VALID_PHONE_NUMBER_WITH_EXTENSION } from './helpers/isViablePhoneNumber.js' | ||
|  | import createExtensionPattern from './helpers/extension/createExtensionPattern.js' | ||
|  | 
 | ||
|  | import parsePreCandidate from './findNumbers/parsePreCandidate.js' | ||
|  | import isValidPreCandidate from './findNumbers/isValidPreCandidate.js' | ||
|  | import isValidCandidate from './findNumbers/isValidCandidate.js' | ||
|  | 
 | ||
|  | /** | ||
|  |  * Regexp of all possible ways to write extensions, for use when parsing. This | ||
|  |  * will be run as a case-insensitive regexp match. Wide character versions are | ||
|  |  * also provided after each ASCII version. There are three regular expressions | ||
|  |  * here. The first covers RFC 3966 format, where the extension is added using | ||
|  |  * ';ext='. The second more generic one starts with optional white space and | ||
|  |  * ends with an optional full stop (.), followed by zero or more spaces/tabs | ||
|  |  * /commas and then the numbers themselves. The other one covers the special | ||
|  |  * case of American numbers where the extension is written with a hash at the | ||
|  |  * end, such as '- 503#'. Note that the only capturing groups should be around | ||
|  |  * the digits that you want to capture as part of the extension, or else parsing | ||
|  |  * will fail! We allow two options for representing the accented o - the | ||
|  |  * character itself, and one in the unicode decomposed form with the combining | ||
|  |  * acute accent. | ||
|  |  */ | ||
|  | export const EXTN_PATTERNS_FOR_PARSING = createExtensionPattern('parsing') | ||
|  | 
 | ||
|  | const WHITESPACE_IN_THE_BEGINNING_PATTERN = new RegExp('^[' + WHITESPACE + ']+') | ||
|  | const PUNCTUATION_IN_THE_END_PATTERN = new RegExp('[' + VALID_PUNCTUATION + ']+$') | ||
|  | 
 | ||
|  | // // Regular expression for getting opening brackets for a valid number
 | ||
|  | // // found using `PHONE_NUMBER_START_PATTERN` for prepending those brackets to the number.
 | ||
|  | // const BEFORE_NUMBER_DIGITS_PUNCTUATION = new RegExp('[' + OPENING_BRACKETS + ']+' + '[' + WHITESPACE + ']*' + '$')
 | ||
|  | 
 | ||
|  | const VALID_PRECEDING_CHARACTER_PATTERN = /[^a-zA-Z0-9]/ | ||
|  | 
 | ||
|  | export default function findPhoneNumbers(text, options, metadata) { | ||
|  | 	/* istanbul ignore if */ | ||
|  | 	if (options === undefined) { | ||
|  | 		options = {} | ||
|  | 	} | ||
|  | 	const search = new PhoneNumberSearch(text, options, metadata) | ||
|  | 	const phones = [] | ||
|  | 	while (search.hasNext()) { | ||
|  | 		phones.push(search.next()) | ||
|  | 	} | ||
|  | 	return phones | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * @return ES6 `for ... of` iterator. | ||
|  |  */ | ||
|  | export function searchPhoneNumbers(text, options, metadata) { | ||
|  | 	/* istanbul ignore if */ | ||
|  | 	if (options === undefined) { | ||
|  | 		options = {} | ||
|  | 	} | ||
|  | 	const search = new PhoneNumberSearch(text, options, metadata) | ||
|  | 	return  { | ||
|  | 		[Symbol.iterator]() { | ||
|  | 			return { | ||
|  | 	    		next: () => { | ||
|  | 	    			if (search.hasNext()) { | ||
|  | 						return { | ||
|  | 							done: false, | ||
|  | 							value: search.next() | ||
|  | 						} | ||
|  | 					} | ||
|  | 					return { | ||
|  | 						done: true | ||
|  | 					} | ||
|  | 	    		} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Extracts a parseable phone number including any opening brackets, etc. | ||
|  |  * @param  {string} text - Input. | ||
|  |  * @return {object} `{ ?number, ?startsAt, ?endsAt }`. | ||
|  |  */ | ||
|  | export class PhoneNumberSearch { | ||
|  | 	constructor(text, options, metadata) { | ||
|  | 		this.text = text | ||
|  | 		// If assigning the `{}` default value is moved to the arguments above,
 | ||
|  | 		// code coverage would decrease for some weird reason.
 | ||
|  | 		this.options = options || {} | ||
|  | 		this.metadata = metadata | ||
|  | 
 | ||
|  | 		// Iteration tristate.
 | ||
|  | 		this.state = 'NOT_READY' | ||
|  | 
 | ||
|  | 		this.regexp = new RegExp(VALID_PHONE_NUMBER_WITH_EXTENSION, 'ig') | ||
|  | 	} | ||
|  | 
 | ||
|  | 	find() { | ||
|  | 		const matches = this.regexp.exec(this.text) | ||
|  | 		if (!matches) { | ||
|  | 			return | ||
|  | 		} | ||
|  | 
 | ||
|  | 		let number = matches[0] | ||
|  | 		let startsAt = matches.index | ||
|  | 
 | ||
|  | 		number = number.replace(WHITESPACE_IN_THE_BEGINNING_PATTERN, '') | ||
|  | 		startsAt += matches[0].length - number.length | ||
|  | 		// Fixes not parsing numbers with whitespace in the end.
 | ||
|  | 		// Also fixes not parsing numbers with opening parentheses in the end.
 | ||
|  | 		// https://github.com/catamphetamine/libphonenumber-js/issues/252
 | ||
|  | 		number = number.replace(PUNCTUATION_IN_THE_END_PATTERN, '') | ||
|  | 
 | ||
|  | 		number = parsePreCandidate(number) | ||
|  | 
 | ||
|  | 		const result = this.parseCandidate(number, startsAt) | ||
|  | 		if (result) { | ||
|  | 			return result | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Tail recursion.
 | ||
|  | 		// Try the next one if this one is not a valid phone number.
 | ||
|  | 		return this.find() | ||
|  | 	} | ||
|  | 
 | ||
|  | 	parseCandidate(number, startsAt) { | ||
|  | 		if (!isValidPreCandidate(number, startsAt, this.text)) { | ||
|  | 			return | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Don't parse phone numbers which are non-phone numbers
 | ||
|  | 		// due to being part of something else (e.g. a UUID).
 | ||
|  | 		// https://github.com/catamphetamine/libphonenumber-js/issues/213
 | ||
|  | 		// Copy-pasted from Google's `PhoneNumberMatcher.js` (`.parseAndValidate()`).
 | ||
|  | 		if (!isValidCandidate(number, startsAt, this.text, this.options.extended ? 'POSSIBLE' : 'VALID')) { | ||
|  | 			return | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// // Prepend any opening brackets left behind by the
 | ||
|  | 		// // `PHONE_NUMBER_START_PATTERN` regexp.
 | ||
|  | 		// const text_before_number = text.slice(this.searching_from, startsAt)
 | ||
|  | 		// const full_number_starts_at = text_before_number.search(BEFORE_NUMBER_DIGITS_PUNCTUATION)
 | ||
|  | 		// if (full_number_starts_at >= 0)
 | ||
|  | 		// {
 | ||
|  | 		// 	number   = text_before_number.slice(full_number_starts_at) + number
 | ||
|  | 		// 	startsAt = full_number_starts_at
 | ||
|  | 		// }
 | ||
|  | 		//
 | ||
|  | 		// this.searching_from = matches.lastIndex
 | ||
|  | 
 | ||
|  | 		const result = parse(number, this.options, this.metadata) | ||
|  | 		if (!result.phone) { | ||
|  | 			return | ||
|  | 		} | ||
|  | 
 | ||
|  | 		result.startsAt = startsAt | ||
|  | 		result.endsAt = startsAt + number.length | ||
|  | 		return result | ||
|  | 	} | ||
|  | 
 | ||
|  | 	hasNext() { | ||
|  | 		if (this.state === 'NOT_READY') { | ||
|  | 			this.last_match = this.find() | ||
|  | 			if (this.last_match) { | ||
|  | 				this.state = 'READY' | ||
|  | 			} else { | ||
|  | 				this.state = 'DONE' | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return this.state === 'READY' | ||
|  | 	} | ||
|  | 
 | ||
|  | 	next() { | ||
|  | 		// Check the state and find the next match as a side-effect if necessary.
 | ||
|  | 		if (!this.hasNext()) { | ||
|  | 			throw new Error('No next element') | ||
|  | 		} | ||
|  | 		// Don't retain that memory any longer than necessary.
 | ||
|  | 		const result = this.last_match | ||
|  | 		this.last_match = null | ||
|  | 		this.state = 'NOT_READY' | ||
|  | 		return result | ||
|  | 	} | ||
|  | } |