Skip to content

Commit bdcf9ad

Browse files
committed
feat: Added findNumbers feature
BREAKING CHANGE: Added core feature
1 parent df36157 commit bdcf9ad

File tree

8 files changed

+356
-122
lines changed

8 files changed

+356
-122
lines changed

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
This library is a pre-compiled version of Google's `libphonenumber`, with a slightly simpler interface. It has a minimal footprint - is by far the smallest libphonenumber-based library available on npmjs, and has no dependencies.
1111

12+
Unlike libphonenumber, it includes a `findNumbers( )` function to find phone numbers in text.
13+
1214
TypeScript typings are provided within the package.
1315

1416
Uses libphonenumber v8.13.43
@@ -32,6 +34,9 @@ Uses libphonenumber v8.13.43
3234
- Dropped Node 12 support
3335
- v6:
3436
- Dropped Node 16 support
37+
- v7:
38+
- Added `findNumbers( )` feature, to find phone numbers in text
39+
- Added support for _short_ numbers
3540

3641

3742
## Comparison with other libraries
@@ -139,6 +144,7 @@ Note that an incorrect (invalid) phone number can still be a valid _short number
139144
```ts
140145
import {
141146
parsePhoneNumber,
147+
findNumbers,
142148
getNumberFrom,
143149
getExample,
144150
getCountryCodeForRegionCode,
@@ -157,6 +163,48 @@ import {
157163
The first argument is the phone number to parse, on either _national_ or _international_ (e164, i.e. prefixed with a `+`) form. If _national_ form, the second argument is required to contain a `regionCode` string property, e.g. 'SE' for Sweden, 'CH' for Switzerland, etc.
158164

159165

166+
### findNumbers
167+
168+
169+
To find (extract) phone numbers in text, use `findNumbers( )`:
170+
171+
```ts
172+
import { findNumbers } from 'awesome-phonenumber'
173+
174+
const text = 'My number is +46 707 123 456, otherwise call +33777777777.';
175+
const numbers = findNumbers( text );
176+
```
177+
178+
The returned list of numbers is of the type `PhoneNumberMatch` such as:
179+
180+
```ts
181+
interface PhoneNumberMatch
182+
{
183+
text: string; // The raw string found
184+
phoneNumber: object; // Same as the result of parsePhoneNumber()
185+
start: number; // Start offset in the text
186+
end: number; // End offset in the text
187+
}
188+
```
189+
190+
A second options argument to `findNumbers( text, options )` can be provided on the form:
191+
192+
```ts
193+
interface FindNumbersOptions
194+
{
195+
defaultRegionCode?: string;
196+
leniency?: FindNumbersLeniency;
197+
maxTries?: number;
198+
}
199+
```
200+
201+
where `FindNumbersLeniency` is an enum of `'valid'` or `'possible'`. The default is `'valid'` meaning that only valid phone numbers are found. If this is set to `'possible'` also possible (but invalid) phone numbers are found.
202+
203+
`defaultRegionCode` can be set (e.g. to `'SE'` for Sweden), in which case phone numbers on _national_ form (i.e. without `+` prefix) will be found, as long as they are from that region.
204+
205+
For really large texts, `maxTries` will set the maximum number of phone numbers to _try_ to find (not necessary actually find).
206+
207+
160208
### getNumberFrom
161209

162210
```ts

index-esm.mjs

+66-61
Large diffs are not rendered by default.

index.d.ts

+54
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,60 @@ export function getRegionCodeForCountryCode( countryCode: number ): string;
107107
export function getSupportedCallingCodes( ): string[ ];
108108
export function getSupportedRegionCodes( ): string[ ];
109109

110+
export type FindNumbersLeniency =
111+
/**
112+
* Phone numbers accepted are possible, but not necessarily valid.
113+
*/
114+
| 'possible'
115+
/**
116+
* Phone numbers accepted are possible and valid.
117+
*/
118+
| 'valid';
119+
120+
export interface FindNumbersOptions
121+
{
122+
/**
123+
* A default region code, to find local (non-e164) formatted phone numbers
124+
*/
125+
defaultRegionCode?: string;
126+
127+
/** Leniency options */
128+
leniency?: FindNumbersLeniency;
129+
130+
/**
131+
* The maximum number of invalid numbers to try before giving up on the text
132+
*/
133+
maxTries?: number;
134+
}
135+
136+
export interface PhoneNumberMatch
137+
{
138+
/** The raw string found */
139+
text: string;
140+
141+
/** The parsed phone number object */
142+
phoneNumber: ParsedPhoneNumber;
143+
144+
/** Start offset of the found number */
145+
start: number;
146+
147+
/** End offset of the found number */
148+
end: number;
149+
}
150+
151+
/**
152+
* Find phone numbers in text.
153+
*
154+
* If the text is expected to have phone numbers for a certain region code,
155+
* the option `defaultRegionCode` can be set. Phone numbers without the
156+
* international prefix `+` will then be found too.
157+
*
158+
* Leniency can be specified using the `leniency` option, in which case
159+
* `maxTries` needs to be set too.
160+
*/
161+
export function findNumbers( text: string, options?: FindNumbersOptions )
162+
: PhoneNumberMatch[ ];
163+
110164
/**
111165
* Get an example phone number, given a region code and a phone number
112166
* {@link PhoneNumberTypes type}.

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ module.exports.getSupportedRegionCodes = module.exports.getSupportedRegionCodes;
4949
module.exports.getExample = module.exports.getExample;
5050
module.exports.getAsYouType = module.exports.getAsYouType;
5151
module.exports.getNumberFrom = module.exports.getNumberFrom;
52+
module.exports.findNumbers = module.exports.findNumbers;

lib/index.js

+63-61
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/esm-outro.js

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
getRegionCodeForCountryCode,
77
getSupportedCallingCodes,
88
getSupportedRegionCodes,
9+
findNumbers,
910
getExample,
1011
getAsYouType,
1112
getNumberFrom,
@@ -42,6 +43,7 @@ export {
4243
getRegionCodeForCountryCode,
4344
getSupportedCallingCodes,
4445
getSupportedRegionCodes,
46+
findNumbers,
4547
getExample,
4648
getAsYouType,
4749
getNumberFrom,

src/index.js

+37
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,43 @@ PhoneNumber.getRegionCodeForCountryCode = function( countryCode )
312312
return regionCode;
313313
}
314314

315+
/** @export */
316+
PhoneNumber.findNumbers = function( text, options )
317+
{
318+
const defaultRegionCode = options?.[ 'defaultRegionCode' ];
319+
const leniency = options?.[ 'leniency' ] ?? 'valid';
320+
const maxTries = options?.[ 'maxTries' ] ?? Number.MAX_SAFE_INTEGER;
321+
322+
const re = /([0-9]+(?: [0-9]+)*)/g;
323+
const res = [ ];
324+
let m;
325+
for ( let i = 0; i < maxTries; ++i )
326+
{
327+
m = re.exec( text );
328+
if ( !m )
329+
break;
330+
331+
let start = m.index;
332+
const end = m.index + m[ 1 ].length;
333+
if ( start > 0 && text.charAt( start - 1 ) === '+' )
334+
--start;
335+
336+
const slice = text.slice( start, end );
337+
338+
const pn = new PhoneNumber( slice, { 'regionCode': defaultRegionCode } );
339+
340+
if ( pn.isValid( ) || ( leniency === 'possible' && pn.isPossible( ) ) )
341+
res.push( {
342+
'text': slice,
343+
'phoneNumber': pn.toJSON( ),
344+
'start': start,
345+
'end': end,
346+
} );
347+
}
348+
349+
return res;
350+
}
351+
315352
function uniq( arr )
316353
{
317354
const lookup = { };

test.in/awesome-phonenumber/new-api.ts

+85
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import PhoneNumberClass, {
77
getSupportedRegionCodes,
88
getSupportedCallingCodes,
99
getNumberFrom,
10+
findNumbers,
1011
} from 'awesome-phonenumber';
1112

1213

@@ -379,6 +380,90 @@ describe( 'short numbers', ( ) =>
379380
} );
380381
} );
381382

383+
describe( 'find numbers', ( ) =>
384+
{
385+
it( 'should not find in empty text', ( ) =>
386+
{
387+
const res = findNumbers( '' );
388+
expect( res.length ).toBe( 0 );
389+
} );
390+
391+
it( 'should not find in text without phone numbers', ( ) =>
392+
{
393+
const res = findNumbers( 'no numbers here 123, not even that' );
394+
expect( res.length ).toBe( 0 );
395+
} );
396+
397+
const textWithNumbers =
398+
'the number is +46 707 123 456 fyi and 0707 555 555 also +11111111111.';
399+
400+
it( 'should find e164 numbers (default valid numbers)', ( ) =>
401+
{
402+
const res = findNumbers( textWithNumbers );
403+
expect( res.length ).toBe( 1 );
404+
expect( res[ 0 ].phoneNumber.number?.e164 ).toBe( '+46707123456' );
405+
expect( res[ 0 ].start ).toBeGreaterThan( 0 );
406+
expect( res[ 0 ].end ).toBeGreaterThan( res[ 0 ].start );
407+
} );
408+
409+
it( 'should find e164 numbers (also possible numbers)', ( ) =>
410+
{
411+
const res = findNumbers( textWithNumbers, { leniency: 'possible' } );
412+
expect( res.length ).toBe( 2 );
413+
expect( res[ 0 ].phoneNumber.number?.e164 ).toBe( '+46707123456' );
414+
expect( res[ 0 ].start ).toBeGreaterThan( 0 );
415+
expect( res[ 0 ].end ).toBeGreaterThan( res[ 0 ].start );
416+
expect( res[ 1 ].phoneNumber.number?.e164 ).toBe( '+11111111111' );
417+
expect( res[ 1 ].start ).toBeGreaterThan( 0 );
418+
expect( res[ 1 ].end ).toBeGreaterThan( res[ 1 ].start );
419+
} );
420+
421+
it( 'should find e164 numbers', ( ) =>
422+
{
423+
const res = findNumbers( textWithNumbers, { defaultRegionCode: 'SE' } );
424+
expect( res.length ).toBe( 2 );
425+
expect( res[ 0 ].phoneNumber.number?.e164 ).toBe( '+46707123456' );
426+
expect( res[ 0 ].start ).toBeGreaterThan( 0 );
427+
expect( res[ 0 ].end ).toBeGreaterThan( res[ 0 ].start );
428+
expect( res[ 1 ].phoneNumber.number?.e164 ).toBe( '+46707555555' );
429+
expect( res[ 1 ].start ).toBeGreaterThan( 0 );
430+
expect( res[ 1 ].end ).toBeGreaterThan( res[ 1 ].start );
431+
} );
432+
433+
it( 'should find e164 numbers (and possible)', ( ) =>
434+
{
435+
const res = findNumbers(
436+
textWithNumbers,
437+
{ defaultRegionCode: 'SE', leniency: 'possible' },
438+
);
439+
expect( res.length ).toBe( 3 );
440+
expect( res[ 0 ].phoneNumber.number?.e164 ).toBe( '+46707123456' );
441+
expect( res[ 0 ].start ).toBeGreaterThan( 0 );
442+
expect( res[ 0 ].end ).toBeGreaterThan( res[ 0 ].start );
443+
expect( res[ 1 ].phoneNumber.number?.e164 ).toBe( '+46707555555' );
444+
expect( res[ 1 ].start ).toBeGreaterThan( 0 );
445+
expect( res[ 1 ].end ).toBeGreaterThan( res[ 1 ].start );
446+
expect( res[ 2 ].phoneNumber.number?.e164 ).toBe( '+11111111111' );
447+
expect( res[ 2 ].start ).toBeGreaterThan( 0 );
448+
expect( res[ 2 ].end ).toBeGreaterThan( res[ 2 ].start );
449+
} );
450+
451+
it( 'should find e164 numbers (and possible), max numbers', ( ) =>
452+
{
453+
const res = findNumbers(
454+
textWithNumbers,
455+
{ defaultRegionCode: 'SE', leniency: 'possible', maxTries: 2 },
456+
);
457+
expect( res.length ).toBe( 2 );
458+
expect( res[ 0 ].phoneNumber.number?.e164 ).toBe( '+46707123456' );
459+
expect( res[ 0 ].start ).toBeGreaterThan( 0 );
460+
expect( res[ 0 ].end ).toBeGreaterThan( res[ 0 ].start );
461+
expect( res[ 1 ].phoneNumber.number?.e164 ).toBe( '+46707555555' );
462+
expect( res[ 1 ].start ).toBeGreaterThan( 0 );
463+
expect( res[ 1 ].end ).toBeGreaterThan( res[ 1 ].start );
464+
} );
465+
} );
466+
382467
describe( 'getNumberFrom', ( ) =>
383468
{
384469
it( 'Using weakmap', ( ) =>

0 commit comments

Comments
 (0)