/** * 多個關鍵詞列表高亮(word_list1,color1,word_list2,color2,...) * @param word_list 關鍵詞列表(例: ["關鍵詞a","關鍵詞b"],不區分大小寫) * @param color 顏色值(例: "#ff0000") * @returns {{render: Function}} * 優先級:word_list1>word_list2>word_list3... * @example * var highlight = new wordHighlight(["B","C"],"#ff0000",["ABC","DEF"],"blue"); * console.log(highlight.render("ABC")); // <b style="color:blue">A</b><b style="color:#ff0000">BC</b> * console.log(highlight.render("Bc,DEF")); // <b style="color:#ff0000">Bc</b>,<b style="color:blue">DEF</b> */ function wordHighlight(word_list,color) { if (!(this instanceof wordHighlight)) { throw 'should be called with the new!'; } var that = this; that.root = null; that.color_list = []; that.color_range = null; var createNode = function(value){ var node = {}; node.value = value || ""; node.next = {}; node.fail = null; node.len = 0; // word長度,0表示非末尾,非0表示word的長度 node.color = -1; return node; } var addWord = function(word,color){ word = word.trim().toLowerCase(); if (!word) { return; } var cur = that.root; var len = word.length; for (var i = 0,v; i < len; i++) { v = word[i]; if (cur.next[v] === undefined) { node = createNode(v); cur.next[v] = node; } cur = cur.next[v]; } cur.len = len; if (cur.color == -1) { cur.color = color; } } var buildFailIndex = function(){ var queue = []; var key; for (key in that.root.next) { that.root.next[key].fail = that.root; queue.push(that.root.next[key]); } while (queue.length) { node = queue.shift(); for (key in node.next) { var child_node = node.next[key]; var val = child_node.value; var p = node.fail; while (p != null) { if (p.next[val] !== undefined) { child_node.fail = p.next[val]; break; } p = p.fail; } if (p === null) { child_node.fail = that.root; } queue.push(child_node); }; } } var mergeRange = function(color,range_list){ if (!range_list || !range_list.length) { return; } range_list = (function(input_list){ // 區間合併 var output_list = []; var tmp = {}, left = -1, right = -1; input_list.forEach(function(range){ var start = range[0], end = range[1]; if (tmp[start] === undefined || tmp[start] < end) { tmp[start] = end; } }); for (var start in tmp) { var end = tmp[start]; if (left == -1) { left = 0 | start; right = end; } if (start > right) { output_list.push([left,right]) left = 0 | start; right = end; } else if (end > right) { right = end; } } if (left != -1) { output_list.push([left, right]) } return output_list; })(range_list); if (that.color_range === null) { that.color_range = {}; range_list.forEach(function(v){ that.color_range[v[0]] = [v[1],color]; }); } else { var xy_list = [0],x,y; for (var x in that.color_range) { xy_list.push(0|x); xy_list.push(that.color_range[x][0]); } xy_list.push(Infinity); //console.log(xy_list); var i = 0,len = range_list.length; do { x = xy_list.shift(); y = xy_list.shift(); } while (x == y); var _x,_y; while (i < len) { _x = range_list[i][0]; _y = range_list[i][1]; if (y < _y) { if (_x < y) { that.color_range[_x < x ? x : _x] = [y,color]; } do { x = xy_list.shift(); y = xy_list.shift(); } while (x == y); continue; } if (_y > x) { that.color_range[_x < x ? x : _x] = [_y,color]; } i++; } } } that.root = createNode(); var args = Array.prototype.slice.call(arguments); while (args.length) { var color = that.color_list.length; word_list = args.shift(); if (!word_list.length) { args.shift(); continue; } that.color_list.push(args.shift()); for (var i in word_list) { addWord(word_list[i],color); } } buildFailIndex(); //console.log(that.root); return { render: function(content){ if (!that.color_list.length) { return content; } var range_list = []; var p = that.root; var len = content.length; var _content = content.toLowerCase(); for (var i = 0,val,temp; i < len; i++) { val = _content[i]; while (p.next[val] === undefined && p != that.root) { p = p.fail; } p = p.next[val] ? p.next[val] : that.root; temp = p; while (temp != that.root) { if (temp.len) { if (!range_list[temp.color]) { range_list[temp.color] = []; } range_list[temp.color].push([i + 1 - temp.len, i + 1]); } temp = temp.fail; } } for (var color in range_list) { //console.log(range_list[color]); mergeRange(color,range_list[color]); } if (that.color_range === null) { return content; } var new_content = '',x = 0,y; for (y in that.color_range) { new_content += content.substring(x,y); x = that.color_range[y][0]; new_content += '<b style="color:' + that.color_list[that.color_range[y][1]] + '">' + content.substring(y,x) + '</b>'; } new_content += content.substring(x); that.color_range = null; return new_content; } } }