Skip to content

Commit 8b9aa3f

Browse files
authored
Add linking for phone numbers (#52)
1 parent 7c2aa8b commit 8b9aa3f

3 files changed

Lines changed: 157 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `linkify` [![pub package](https://img.shields.io/pub/v/linkify.svg)](https://pub.dartlang.org/packages/linkify)
22

3-
Low-level link (text, URLs, emails) parsing library in Dart.
3+
Low-level link (text, URLs, emails, phone numbers) parsing library in Dart.
44

55
Required Dart >=2.12 (has null-safety support).
66

lib/src/phone_number.dart

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import 'package:linkify/linkify.dart';
2+
3+
final _phoneNumberRegex = RegExp(
4+
r'^(.*?)((tel:)?[+]*[\s/0-9]{8,15})',
5+
caseSensitive: false,
6+
dotAll: true,
7+
);
8+
9+
class PhoneNumberLinkifier extends Linkifier {
10+
const PhoneNumberLinkifier();
11+
12+
@override
13+
List<LinkifyElement> parse(elements, options) {
14+
final list = <LinkifyElement>[];
15+
16+
elements.forEach((element) {
17+
if (element is TextElement) {
18+
var match = _phoneNumberRegex.firstMatch(element.text);
19+
20+
if (match == null) {
21+
list.add(element);
22+
} else {
23+
final text = element.text.replaceFirst(match.group(0)!, '');
24+
25+
if (match.group(1)?.isNotEmpty == true) {
26+
list.add(TextElement(match.group(1)!));
27+
}
28+
29+
if (match.group(2)?.isNotEmpty == true) {
30+
list.add(PhoneNumberElement(
31+
match.group(2)!.replaceFirst(RegExp(r'tel:'), ''),
32+
));
33+
}
34+
35+
if (text.isNotEmpty) {
36+
list.addAll(parse([TextElement(text)], options));
37+
}
38+
}
39+
} else {
40+
list.add(element);
41+
}
42+
});
43+
44+
return list;
45+
}
46+
}
47+
48+
/// Represents an element containing a phone number
49+
class PhoneNumberElement extends LinkableElement {
50+
final String phoneNumber;
51+
52+
PhoneNumberElement(this.phoneNumber)
53+
: super(
54+
phoneNumber,
55+
'tel:$phoneNumber',
56+
);
57+
58+
@override
59+
String toString() {
60+
return "PhoneNumberElement: '$phoneNumber' ($text)";
61+
}
62+
63+
@override
64+
bool operator ==(other) => equals(other);
65+
66+
@override
67+
bool equals(other) =>
68+
other is PhoneNumberElement &&
69+
super.equals(other) &&
70+
other.phoneNumber == phoneNumber;
71+
}

test/linkify_test.dart

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'package:collection/collection.dart';
22
import 'package:linkify/linkify.dart';
33
import 'package:test/test.dart';
44

5+
import '../lib/src/phone_number.dart';
6+
57
final listEqual = const ListEquality().equals;
68

79
void expectListEqual(List actual, List expected) {
@@ -215,14 +217,28 @@ void main() {
215217

216218
test('Parses user tag', () {
217219
expectListEqual(
218-
linkify("@example"),
220+
linkify(
221+
"@example",
222+
linkifiers: [
223+
UrlLinkifier(),
224+
EmailLinkifier(),
225+
UserTagLinkifier(),
226+
],
227+
),
219228
[UserTagElement("@example")],
220229
);
221230
});
222231

223232
test('Parses email, link, and user tag', () {
224233
expectListEqual(
225-
linkify("person@example.com at https://google.com @example"),
234+
linkify(
235+
"person@example.com at https://google.com @example",
236+
linkifiers: [
237+
UrlLinkifier(),
238+
EmailLinkifier(),
239+
UserTagLinkifier(),
240+
],
241+
),
226242
[
227243
EmailElement("person@example.com"),
228244
TextElement(" at "),
@@ -232,4 +248,71 @@ void main() {
232248
],
233249
);
234250
});
251+
252+
test('Parses invalid phone number', () {
253+
expectListEqual(
254+
linkify(
255+
"This is an invalid numbers 17.00",
256+
linkifiers: [
257+
UrlLinkifier(),
258+
EmailLinkifier(),
259+
PhoneNumberLinkifier(),
260+
],
261+
),
262+
[
263+
TextElement("This is an invalid numbers 17.00"),
264+
],
265+
);
266+
});
267+
268+
test('Parses german phone number', () {
269+
expectListEqual(
270+
linkify(
271+
"This is a german example number +49 30 901820",
272+
linkifiers: [
273+
UrlLinkifier(),
274+
EmailLinkifier(),
275+
PhoneNumberLinkifier(),
276+
],
277+
),
278+
[
279+
TextElement("This is a german example number "),
280+
PhoneNumberElement("+49 30 901820"),
281+
],
282+
);
283+
});
284+
285+
test('Parses seattle phone number', () {
286+
expectListEqual(
287+
linkify(
288+
"This is a seattle example number +1 206 555 0100",
289+
linkifiers: [
290+
UrlLinkifier(),
291+
EmailLinkifier(),
292+
PhoneNumberLinkifier(),
293+
],
294+
),
295+
[
296+
TextElement("This is a seattle example number "),
297+
PhoneNumberElement("+1 206 555 0100"),
298+
],
299+
);
300+
});
301+
302+
test('Parses uk phone number', () {
303+
expectListEqual(
304+
linkify(
305+
"This is an example number from uk: +44 113 496 0000",
306+
linkifiers: [
307+
UrlLinkifier(),
308+
EmailLinkifier(),
309+
PhoneNumberLinkifier(),
310+
],
311+
),
312+
[
313+
TextElement("This is an example number from uk: "),
314+
PhoneNumberElement("+44 113 496 0000"),
315+
],
316+
);
317+
});
235318
}

0 commit comments

Comments
 (0)