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