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
						
					
					
				| // 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
 | |
| 	}
 | |
| } |