方案
我正在尝试开发一个Javascript文本高亮功能。 它在输入中接收一个要在里面搜索的文本,一个要搜索的标记数组,一个用于包装找到的匹配的类:
var fmk = fmk || {};
fmk.highlight = function (target, tokens, cls) {
var token, re;
if (tokens.length > 0) {
token = tokens.pop();
re = new RegExp(token, "gi");
return this.highlight(
target.replace(re, function (matched) {
return "<span class=\"" + cls + "\">" + matched + "</span>";
}), tokens, cls);
}
else { return target; }
};
它基于递归替换,在找到的匹配项周围包含<span>
标记。
问题
如果有两个令牌,后者是前一个的子串,那么只有后一个令牌会被高亮显示。在jsFiddle示例中尝试这些标记:'ab b'。
如果标记包含包装序列的子字符串(即<span class="[className]"></span>
)和另一个匹配标记,则然后突出显示失败并返回脏结果。在jsFiddle示例中尝试这些标记:'red ab'。
注意在实际应用程序中允许使用单个字符标记。
问题
如何避免这些错误?我想出了这些方法:
要预处理令牌,请删除作为其他子串的令牌。缺点:在n个令牌的情况下,它需要在预处理阶段进行O(n^2)次搜索;好的比赛被切断了。
在应用包装器之前预处理匹配,以便仅切断子串匹配。缺点:再次,需要进一步的计算。无论如何,我不知道从哪里开始实现这个替换回调函数。
答案 0 :(得分:2)
我认为处理这个问题的方法是循环遍历元素的所有后代,检查它是否是文本节点,并替换用span / class包装的相应内容。
var MyApp = {};
MyApp.highlighter = (function () {
"use strict";
var checkAndReplace, func,
id = {
container: "container",
tokens: "tokens",
all: "all",
token: "token",
className: "className",
sensitiveSearch: "sensitiveSearch"
};
checkAndReplace = function (node, tokenArr, classNameAll, sensitiveSearchAll) {
var nodeVal = node.nodeValue, parentNode = node.parentNode,
i, j, curToken, myToken, myClassName, mySensitiveSearch,
finalClassName, finalSensitiveSearch,
foundIndex, begin, matched, end,
textNode, span;
for (i = 0, j = tokenArr.length; i < j; i++) {
curToken = tokenArr[i];
myToken = curToken[id.token];
myClassName = curToken[id.className];
mySensitiveSearch = curToken[id.sensitiveSearch];
finalClassName = (classNameAll ? myClassName + " " + classNameAll : myClassName);
finalSensitiveSearch = (typeof sensitiveSearchAll !== "undefined" ? sensitiveSearchAll : mySensitiveSearch);
if (finalSensitiveSearch) {
foundIndex = nodeVal.indexOf(myToken);
} else {
foundIndex = nodeVal.toLowerCase().indexOf(myToken.toLowerCase());
}
if (foundIndex > -1) {
begin = nodeVal.substring(0, foundIndex);
matched = nodeVal.substr(foundIndex, myToken.length);
end = nodeVal.substring(foundIndex + myToken.length, nodeVal.length);
if (begin) {
textNode = document.createTextNode(begin);
parentNode.insertBefore(textNode, node);
}
span = document.createElement("span");
span.className += finalClassName;
span.appendChild(document.createTextNode(matched));
parentNode.insertBefore(span, node);
if (end) {
textNode = document.createTextNode(end);
parentNode.insertBefore(textNode, node);
}
parentNode.removeChild(node);
}
}
};
func = function (options) {
var iterator,
tokens = options[id.tokens],
allClassName = options[id.all][id.className],
allSensitiveSearch = options[id.all][id.sensitiveSearch];
iterator = function (p) {
var children = Array.prototype.slice.call(p.childNodes),
i, cur;
if (children.length) {
for (i = 0; i < children.length; i++) {
cur = children[i];
if (cur.nodeType === 3) {
checkAndReplace(cur, tokens, allClassName, allSensitiveSearch);
} else if (cur.nodeType === 1) {
iterator(cur);
}
}
}
};
iterator(options[id.container]);
};
return func;
})();
window.onload = function () {
var container = document.getElementById("container");
MyApp.highlighter({
container: container,
all: {
className: "highlighter"
},
tokens: [{
token: "sd",
className: "highlight-sd",
sensitiveSearch: false
}, {
token: "SA",
className: "highlight-SA",
sensitiveSearch: true
}]
});
};
DEMO: http://jsfiddle.net/UWQ6r/1/
我进行了设置,因此您可以更改id
中的值,以便在传递给{}
的{{1}}中使用不同的键。
highlighter
对象中的两个设置指的是无论如何都要添加的类,以及区分大小写的搜索覆盖。对于每个令牌,您可以指定令牌,类以及匹配是否区分大小写。
参考文献:
all
:https://developer.mozilla.org/en-US/docs/DOM/Node.nodeType nodeType
:https://developer.mozilla.org/en-US/docs/DOM/Node.childNodes childNodes
:https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substr substr
:https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substring substring
:https://developer.mozilla.org/en-US/docs/DOM/Node.insertBefore 答案 1 :(得分:1)
这似乎对我有用:
(JsFiddle demo中的第17行)
问题1:var tokens = [['ab','b'].join("|")];
问题2:var tokens = ['<span'.replace(/</g,"<")];
所有在一起,然后:
var tokens = [[..my tokens..].sort().join("|").replace(/</g,"<")];
(顺便说一下,我测试了'"'
,'"s'
或'span'
这样的令牌,但它们似乎工作得很好。另外,我不确定为什么{{1}这里很重要,但是因为我喜欢接近原始代码而离开了它。)