1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const LINKIFY_TIMEOUT = 0;
function Linkifier() {
this._linkifyTimer = null;
this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g;
}
Linkifier.prototype = {
_buildAnchor : function(aDoc, aNumberText) {
let anchorNode = aDoc.createElement("a");
let cleanedText = "";
for (let i = 0; i < aNumberText.length; i++) {
let c = aNumberText.charAt(i);
if ((c >= '0' && c <= '9') || c == '+') //assuming there is only the leading '+'.
cleanedText += c;
}
anchorNode.setAttribute("href", "tel:" + cleanedText);
let nodeText = aDoc.createTextNode(aNumberText);
anchorNode.appendChild(nodeText);
return anchorNode;
},
_linkifyNodeNumbers : function(aNodeToProcess, aDoc) {
let parent = aNodeToProcess.parentNode;
let nodeText = aNodeToProcess.nodeValue;
// Replacing the original text node with a sequence of
// |text before number|anchor with number|text after number nodes.
// Each step a couple of (optional) text node and anchor node are appended.
let anchorNode = null;
let m = null;
let startIndex = 0;
let prevNode = null;
while (m = this._phoneRegex.exec(nodeText)) {
anchorNode = this._buildAnchor(aDoc, nodeText.substr(m.index, m[0].length));
let textExistsBeforeNumber = (m.index > startIndex);
let nodeToAdd = null;
if (textExistsBeforeNumber)
nodeToAdd = aDoc.createTextNode(nodeText.substr(startIndex, m.index - startIndex));
else
nodeToAdd = anchorNode;
if (!prevNode) // first time, need to replace the whole node with the first new one.
parent.replaceChild(nodeToAdd, aNodeToProcess);
else
parent.insertBefore(nodeToAdd, prevNode.nextSibling); //inserts after.
if (textExistsBeforeNumber) // if we added the text node before the anchor, we still need to add the anchor node.
parent.insertBefore(anchorNode, nodeToAdd.nextSibling);
// next nodes need to be appended to this node.
prevNode = anchorNode;
startIndex = m.index + m[0].length;
}
// if some text is remaining after the last anchor.
if (startIndex > 0 && startIndex < nodeText.length) {
let lastNode = aDoc.createTextNode(nodeText.substr(startIndex));
parent.insertBefore(lastNode, prevNode.nextSibling);
return lastNode;
}
return anchorNode;
},
linkifyNumbers: function(aDoc) {
// Removing any installed timer in case the page has changed and a previous timer is still running.
if (this._linkifyTimer) {
clearTimeout(this._linkifyTimer);
this._linkifyTimer = null;
}
let filterNode = function (node) {
if (node.parentNode.tagName != 'A' &&
node.parentNode.tagName != 'SCRIPT' &&
node.parentNode.tagName != 'NOSCRIPT' &&
node.parentNode.tagName != 'STYLE' &&
node.parentNode.tagName != 'APPLET' &&
node.parentNode.tagName != 'TEXTAREA')
return NodeFilter.FILTER_ACCEPT;
else
return NodeFilter.FILTER_REJECT;
}
let nodeWalker = aDoc.createTreeWalker(aDoc.body, NodeFilter.SHOW_TEXT, filterNode, false);
let parseNode = function() {
let node = nodeWalker.nextNode();
if (!node) {
this._linkifyTimer = null;
return;
}
let lastAddedNode = this._linkifyNodeNumbers(node, aDoc);
// we assign a different timeout whether the node was processed or not.
if (lastAddedNode) {
nodeWalker.currentNode = lastAddedNode;
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
} else {
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
}
}.bind(this);
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
}
};
|