VulpesOS/gaia/shared/js/fb/fb_tel_index.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

279 lines
7.3 KiB
JavaScript
Executable file

'use strict';
/* globals utils */
/* exported TelIndexer */
// This object implements a prefix tree (aka trie) for FB tel numbers
function TrieNode(number) {
this.keys = Object.create(null);
this.leaves = null;
this.value = number;
}
var TelIndexer = {
_MIN_TEL_LENGTH: 3,
_INSERT_MODE: 'insert',
_indexOf: function(array, value, byPrefix) {
var out = -1;
if (!Array.isArray(array)) {
return out;
}
for (var j = 0, l = array.length; j < l; j++) {
if (array[j].value === value || (byPrefix &&
array[j].value.startsWith(value))) {
out = j;
break;
}
}
return out;
},
_getStringVariants: function(number) {
var out = [];
var limit = number.length - this._MIN_TEL_LENGTH + 1;
for (var j = 0; j < limit; j++) {
out.push(number.substr(j));
}
return out;
},
// Allows to index the number passed as parameter
index: function(tree, number, dsId) {
var self = this;
var variants = this._getStringVariants(number);
variants.forEach(function(aVariant) {
self._insert(tree, aVariant, dsId);
});
},
// Inserts a substring in a node. dsId is the object id (uid) associated
_insertInNode: function(node, str, dsId) {
if (str.length === 0) {
return;
}
var currentStr = str;
var insertPointFound = false;
var insertObj;
var pointerOriginal = str.length;
while (!insertPointFound && currentStr.length > 0) {
var inextObj = this._indexOf(node.leaves, currentStr, 'prefix');
if (inextObj !== -1) {
insertPointFound = true;
insertObj = node.leaves[inextObj];
}
else {
currentStr = currentStr.substring(0, currentStr.length - 1);
}
pointerOriginal--;
}
if (insertPointFound) {
if (insertObj.value === str) {
// Exact insertion point
insertObj.keys[dsId] = true;
}
else {
var keys = Object.keys(insertObj.keys);
// Let's check what's the gap
var newString = insertObj.value.substring(0, currentStr.length);
var restString = insertObj.value.substring(currentStr.length);
insertObj.value = newString;
if (restString.length > 0) {
var newNode = new TrieNode(restString);
for (var j = 0; j < keys.length; j++) {
newNode.keys[keys[j]] = true;
}
if (Array.isArray(insertObj.leaves)) {
newNode.leaves = [];
insertObj.leaves.forEach(function(aLeave) {
newNode.leaves.push(aLeave);
});
}
insertObj.leaves = [];
insertObj.leaves.push(newNode);
}
insertObj.keys[dsId] = true;
var restOriginal = str.substring(pointerOriginal + 1);
// And the rest of the original string is inserted in the insertObj
this._insertInNode(insertObj, restOriginal, dsId);
}
}
else {
var nextObj = new TrieNode(str);
nextObj.keys[dsId] = true;
node.leaves = node.leaves || [];
node.leaves.push(nextObj);
}
},
// Compare function used for binary search
_compare: function(a, b) {
return a.localeCompare(b);
},
// Find the first level node (corresponding to the 3 first numbers)
_getFirstLevelNode: function(tree, str, mode) {
var out = null;
var index;
if (mode === this._INSERT_MODE) {
index = this._indexOf(tree, str);
if (index !== -1) {
out = tree[index];
}
}
else {
index = utils.binarySearch(str, tree, {
compareFunction: this._compare,
arrayField: 'value'
});
if (index.length > 0) {
out = tree[index[0]];
}
}
return out;
},
// Orders the tree enabling binary search on the first levels
orderTree: function(tree) {
tree.sort(function(a, b) {
return a.value.localeCompare(b.value);
});
},
// Inserts a number on the tree
_insert: function(tree, str, dsId) {
var firstThreeStr = str.substring(0, this._MIN_TEL_LENGTH);
var rootObj = this._getFirstLevelNode(tree, firstThreeStr,
this._INSERT_MODE);
if (!rootObj) {
rootObj = new TrieNode(firstThreeStr);
tree.push(rootObj);
}
rootObj.keys[dsId] = true;
this._insertInNode(rootObj, str.substring(this._MIN_TEL_LENGTH), dsId);
},
// Search a substring in a node
_searchInNode: function(node, str) {
var currentStr = str;
var found = false;
var pointerOriginal = str.length;
var searchObj;
while (!found && currentStr.length > 0) {
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
if (idxObj !== -1) {
found = true;
searchObj = node.leaves[idxObj];
}
else {
currentStr = currentStr.substring(0, currentStr.length - 1);
}
pointerOriginal--;
}
if (found) {
if (searchObj.value === str) {
return searchObj;
}
// Let's calculate what is the gap
var restString = str.substr(pointerOriginal + 1);
if (restString.length > 0) {
return this._searchInNode(searchObj, restString);
}
else {
return searchObj;
}
}
else {
return null;
}
},
// Search for a number (which can be partial) on the tree
search: function(tree, number) {
var out = [];
var totalLength = number.length;
var MIN_TEL_LENGTH = this._MIN_TEL_LENGTH;
if (totalLength >= MIN_TEL_LENGTH) {
var firstThreeStr = number.substring(0, MIN_TEL_LENGTH);
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
if (rootObj) {
if (totalLength === MIN_TEL_LENGTH) {
out = Object.keys(rootObj.keys);
}
else {
var node = this._searchInNode(rootObj,
number.substring(MIN_TEL_LENGTH));
if (node) {
out = Object.keys(node.keys);
}
}
}
}
return out;
},
// Removes a number from the tree
remove: function(tree, number, dsId) {
var self = this;
var variants = this._getStringVariants(number);
variants.forEach(function(aVariant) {
self._unIndex(tree, aVariant, dsId);
});
},
// Removes a partial string from a node
_removeInNode: function(node, str, dsId) {
if (str.length === 0) {
return;
}
var currentStr = str;
var found = false;
var pointerOriginal = str.length;
var searchObj;
while (!found && currentStr.length > 0) {
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
if (idxObj !== -1) {
found = true;
searchObj = node.leaves[idxObj];
}
else {
currentStr = currentStr.substring(0, currentStr.length - 1);
}
pointerOriginal--;
}
if (found) {
delete searchObj.keys[dsId];
this._removeInNode(searchObj, str.substr(pointerOriginal + 1), dsId);
}
},
// Removes a number from the tree
// TODO: Compact the tree when a number is removed
_unIndex: function(tree, number, dsId) {
var firstThreeStr = number.substring(0, this._MIN_TEL_LENGTH);
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
if (rootObj) {
delete rootObj.keys[dsId];
this._removeInNode(rootObj, number.substr(this._MIN_TEL_LENGTH), dsId);
}
}
};