aca
Version:
Aho-Corasick automation for Node.js, Unicode Supported.
179 lines (159 loc) • 3.9 kB
JavaScript
function resetMachine(charset){
var lowestCharCodeAt = 48;
var highestCharCodeAt = 102;
var MaxChars;
var Out, FF, GF;
if(charset == 'ascii'){
lowestCharCodeAt = 0;
highestCharCodeAt = 127;
}
MaxChars = highestCharCodeAt - lowestCharCodeAt + 1;
Out = [0];
FF = [-1];
GF = [new Array(MaxChars).fill(-1)];
return {
Out,
FF,
GF,
lowestCharCodeAt,
highestCharCodeAt,
}
}
function buildMatchingMachine(keywords, machine){
var states = 1;
var {Out, FF, GF, highestCharCodeAt, lowestCharCodeAt} = machine;
var MaxChars = highestCharCodeAt - lowestCharCodeAt + 1;
keywords.forEach((word,i)=>{
let state = 0;
for (let j = 0; j < word.length; j++){
let c = word[j].charCodeAt() - lowestCharCodeAt;
if(!GF[states]){
GF[states] = new Array(MaxChars).fill(-1);
Out[states] = 0;
FF[states] = -1;
}
if (GF[state][c] == -1){
GF[state][c] = states++;
}
state = GF[state][c];
}
Out[state] += i;
});
for (let c = 0; c < MaxChars; c++){
if (GF[0][c] == -1){
GF[0][c] = 0;
}
}
var q = [];
for (let c = 0; c < MaxChars; c++){
if (GF[0][c] != -1 && GF[0][c] != 0){
FF[GF[0][c]] = 0;
q.push(GF[0][c]);
}
}
while (q.length){
let state = q[0];
delete q[0];
q = Object.values(q);
for (let c = 0; c < MaxChars; c++){
if (GF[state][c] != -1){
let failure = FF[state];
while (GF[failure][c] == -1){
failure = FF[failure];
}
failure = GF[failure][c];
FF[GF[state][c]] = failure;
Out[GF[state][c]] += Out[failure];
q.push(GF[state][c]);
}
}
}
return states;
}
function toUnicode(str){
var nicode = '', hex, charCode;
str = String(str);
if(str !== ''){
for (let i = 0; i < str.length; i++){
hex = parseInt(str[i].charCodeAt(0), 10).toString(16);
charCode = '0'.repeat(4 - hex.length) + hex;
nicode += charCode;
}
}
return nicode;
}
function findNext(state, nextInput, machine){
var {Out, FF, GF, lowestCharCodeAt} = machine;
var answer = state;
var c = nextInput.charCodeAt() - lowestCharCodeAt;
while (GF[answer][c] == -1){
answer = FF[answer];
}
return GF[answer][c];
}
function find(keywords, text, charset){
var state = 0;
var matches = {};
var positions = [];
var count = {};
var catches = {};
var _text = '';
var _keywords = [''];
var unicodeKeywords = [''];
var _charset = charset && String(charset).toLowerCase() == 'ascii' ? 'ascii' : 'unicode';
var machine = resetMachine(_charset);
var {Out, FF, GF} = machine;
var isASCII = _charset == 'ascii' ? true : false;
keywords.forEach((keyword)=>{
if(keyword!== ''){
if(!isASCII){
unicodeKeywords.push(toUnicode(keyword));
}
_keywords.push(keyword);
}
});
try{
buildMatchingMachine(isASCII ? _keywords : unicodeKeywords, machine);
text = isASCII ? text : toUnicode(text);
for (let i = 0; i < text.length; i++){
state = findNext(state, text[i], machine);
_text += text[i];
if (Out[state] == 0){
continue;
}
for (let j = 1; j < _keywords.length; j++){
let keywordText = isASCII ? _keywords[j] : unicodeKeywords[j];
if (_text.endsWith( keywordText )){
let pos = (i + 1 - keywordText.length) / (isASCII ? 1 : 4);
let keyword = _keywords[j];
let catchKeywords = catches[pos] || [];
if(catchKeywords.indexOf(keyword) < 0){
catchKeywords.push(keyword);
catches[pos] = catchKeywords;
}
}
}
}
Object.keys(catches).forEach((pos)=>{
var item = catches[pos];
pos = Number(pos);
for (let i = 0; i < item.length; i++) {
let keyword = item[i];
matches[keyword] = matches[keyword] || [];
matches[keyword].push(pos);
count[keyword] = (count[keyword] || 0) + 1;
}
positions.push([pos,item]);
});
}catch(e){
console.log('Invalid charset: '+ charset);
}
return {
matches,
positions,
count
};
}
module.exports = {
find,
}