findPhoneNumbersInitialImplementation.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.PhoneNumberSearch = exports.EXTN_PATTERNS_FOR_PARSING = void 0;
  6. exports["default"] = findPhoneNumbers;
  7. exports.searchPhoneNumbers = searchPhoneNumbers;
  8. var _constants = require("../constants.js");
  9. var _parse = _interopRequireDefault(require("../parse.js"));
  10. var _isViablePhoneNumber = require("../helpers/isViablePhoneNumber.js");
  11. var _createExtensionPattern = _interopRequireDefault(require("../helpers/extension/createExtensionPattern.js"));
  12. var _parsePreCandidate = _interopRequireDefault(require("../findNumbers/parsePreCandidate.js"));
  13. var _isValidPreCandidate = _interopRequireDefault(require("../findNumbers/isValidPreCandidate.js"));
  14. var _isValidCandidate = _interopRequireDefault(require("../findNumbers/isValidCandidate.js"));
  15. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
  16. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  17. function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
  18. function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
  19. function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  20. /**
  21. * Regexp of all possible ways to write extensions, for use when parsing. This
  22. * will be run as a case-insensitive regexp match. Wide character versions are
  23. * also provided after each ASCII version. There are three regular expressions
  24. * here. The first covers RFC 3966 format, where the extension is added using
  25. * ';ext='. The second more generic one starts with optional white space and
  26. * ends with an optional full stop (.), followed by zero or more spaces/tabs
  27. * /commas and then the numbers themselves. The other one covers the special
  28. * case of American numbers where the extension is written with a hash at the
  29. * end, such as '- 503#'. Note that the only capturing groups should be around
  30. * the digits that you want to capture as part of the extension, or else parsing
  31. * will fail! We allow two options for representing the accented o - the
  32. * character itself, and one in the unicode decomposed form with the combining
  33. * acute accent.
  34. */
  35. var EXTN_PATTERNS_FOR_PARSING = (0, _createExtensionPattern["default"])('parsing');
  36. exports.EXTN_PATTERNS_FOR_PARSING = EXTN_PATTERNS_FOR_PARSING;
  37. var WHITESPACE_IN_THE_BEGINNING_PATTERN = new RegExp('^[' + _constants.WHITESPACE + ']+');
  38. var PUNCTUATION_IN_THE_END_PATTERN = new RegExp('[' + _constants.VALID_PUNCTUATION + ']+$'); // // Regular expression for getting opening brackets for a valid number
  39. // // found using `PHONE_NUMBER_START_PATTERN` for prepending those brackets to the number.
  40. // const BEFORE_NUMBER_DIGITS_PUNCTUATION = new RegExp('[' + OPENING_BRACKETS + ']+' + '[' + WHITESPACE + ']*' + '$')
  41. var VALID_PRECEDING_CHARACTER_PATTERN = /[^a-zA-Z0-9]/;
  42. function findPhoneNumbers(text, options, metadata) {
  43. /* istanbul ignore if */
  44. if (options === undefined) {
  45. options = {};
  46. }
  47. var search = new PhoneNumberSearch(text, options, metadata);
  48. var phones = [];
  49. while (search.hasNext()) {
  50. phones.push(search.next());
  51. }
  52. return phones;
  53. }
  54. /**
  55. * @return ES6 `for ... of` iterator.
  56. */
  57. function searchPhoneNumbers(text, options, metadata) {
  58. /* istanbul ignore if */
  59. if (options === undefined) {
  60. options = {};
  61. }
  62. var search = new PhoneNumberSearch(text, options, metadata);
  63. return _defineProperty({}, Symbol.iterator, function () {
  64. return {
  65. next: function next() {
  66. if (search.hasNext()) {
  67. return {
  68. done: false,
  69. value: search.next()
  70. };
  71. }
  72. return {
  73. done: true
  74. };
  75. }
  76. };
  77. });
  78. }
  79. /**
  80. * Extracts a parseable phone number including any opening brackets, etc.
  81. * @param {string} text - Input.
  82. * @return {object} `{ ?number, ?startsAt, ?endsAt }`.
  83. */
  84. var PhoneNumberSearch = /*#__PURE__*/function () {
  85. function PhoneNumberSearch(text, options, metadata) {
  86. _classCallCheck(this, PhoneNumberSearch);
  87. this.text = text; // If assigning the `{}` default value is moved to the arguments above,
  88. // code coverage would decrease for some weird reason.
  89. this.options = options || {};
  90. this.metadata = metadata; // Iteration tristate.
  91. this.state = 'NOT_READY';
  92. this.regexp = new RegExp(_isViablePhoneNumber.VALID_PHONE_NUMBER_WITH_EXTENSION, 'ig');
  93. }
  94. _createClass(PhoneNumberSearch, [{
  95. key: "find",
  96. value: function find() {
  97. var matches = this.regexp.exec(this.text);
  98. if (!matches) {
  99. return;
  100. }
  101. var number = matches[0];
  102. var startsAt = matches.index;
  103. number = number.replace(WHITESPACE_IN_THE_BEGINNING_PATTERN, '');
  104. startsAt += matches[0].length - number.length; // Fixes not parsing numbers with whitespace in the end.
  105. // Also fixes not parsing numbers with opening parentheses in the end.
  106. // https://github.com/catamphetamine/libphonenumber-js/issues/252
  107. number = number.replace(PUNCTUATION_IN_THE_END_PATTERN, '');
  108. number = (0, _parsePreCandidate["default"])(number);
  109. var result = this.parseCandidate(number, startsAt);
  110. if (result) {
  111. return result;
  112. } // Tail recursion.
  113. // Try the next one if this one is not a valid phone number.
  114. return this.find();
  115. }
  116. }, {
  117. key: "parseCandidate",
  118. value: function parseCandidate(number, startsAt) {
  119. if (!(0, _isValidPreCandidate["default"])(number, startsAt, this.text)) {
  120. return;
  121. } // Don't parse phone numbers which are non-phone numbers
  122. // due to being part of something else (e.g. a UUID).
  123. // https://github.com/catamphetamine/libphonenumber-js/issues/213
  124. // Copy-pasted from Google's `PhoneNumberMatcher.js` (`.parseAndValidate()`).
  125. if (!(0, _isValidCandidate["default"])(number, startsAt, this.text, this.options.extended ? 'POSSIBLE' : 'VALID')) {
  126. return;
  127. } // // Prepend any opening brackets left behind by the
  128. // // `PHONE_NUMBER_START_PATTERN` regexp.
  129. // const text_before_number = text.slice(this.searching_from, startsAt)
  130. // const full_number_starts_at = text_before_number.search(BEFORE_NUMBER_DIGITS_PUNCTUATION)
  131. // if (full_number_starts_at >= 0)
  132. // {
  133. // number = text_before_number.slice(full_number_starts_at) + number
  134. // startsAt = full_number_starts_at
  135. // }
  136. //
  137. // this.searching_from = matches.lastIndex
  138. var result = (0, _parse["default"])(number, this.options, this.metadata);
  139. if (!result.phone) {
  140. return;
  141. }
  142. result.startsAt = startsAt;
  143. result.endsAt = startsAt + number.length;
  144. return result;
  145. }
  146. }, {
  147. key: "hasNext",
  148. value: function hasNext() {
  149. if (this.state === 'NOT_READY') {
  150. this.last_match = this.find();
  151. if (this.last_match) {
  152. this.state = 'READY';
  153. } else {
  154. this.state = 'DONE';
  155. }
  156. }
  157. return this.state === 'READY';
  158. }
  159. }, {
  160. key: "next",
  161. value: function next() {
  162. // Check the state and find the next match as a side-effect if necessary.
  163. if (!this.hasNext()) {
  164. throw new Error('No next element');
  165. } // Don't retain that memory any longer than necessary.
  166. var result = this.last_match;
  167. this.last_match = null;
  168. this.state = 'NOT_READY';
  169. return result;
  170. }
  171. }]);
  172. return PhoneNumberSearch;
  173. }();
  174. exports.PhoneNumberSearch = PhoneNumberSearch;
  175. //# sourceMappingURL=findPhoneNumbersInitialImplementation.js.map