UNPKG

aca

Version:

Aho-Corasick automation for Node.js, Unicode Supported.

179 lines (159 loc) 3.9 kB
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, }