1

设想

我正在尝试开发一个 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 演示

问题

  1. 如果有两个标记,而后者是前者的子字符串,那么只有后一个标记会被高亮显示。在 jsFiddle 示例中尝试这些标记:'ab b'。

  2. 如果标记包含包装序列的子字符串(即<span class="[className]"></span>)和另一个匹配标记,突出显示失败并返回脏结果。在 jsFiddle 示例中尝试这些标记:'red ab'。

请注意,在实际应用中允许使用单字符标记。

问题

如何避免这些错误?我想出了这些方法:

  • 预处理标记,删除作为其他子字符串的标记。缺点:在n个token的情况下,在预处理阶段需要O(n^2)次搜索;好的匹配被切断。

  • 在应用包装器之前预处理匹配,以便仅切断子字符串匹配。缺点:同样需要进一步的计算。无论如何,我不知道从哪里开始在替换回调函数中实现这个。

4

2 回答 2

2

我认为处理这个问题的方法是遍历一个元素的所有后代,检查它是否是一个文本节点,并用一个跨度/类替换适当的内容。

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
        }]
    });
};

演示:http: //jsfiddle.net/UWQ6r/1/

我对其进行了设置,以便您可以更改其中的值,id以便您可以使用{}传递给的不同键highlighter

对象中的两个设置all指的是无论如何添加的类,以及区分大小写的搜索覆盖。对于每个标记,您指定标记、类以及匹配是否应区分大小写。

参考:

于 2013-05-13T17:30:52.093 回答
1

这似乎对我有用:

(您的JsFiddle 演示中的第 17 行)

问题一:var tokens = [['ab','b'].join("|")];

问题 2:var tokens = ['<span'.replace(/</g,"&lt;")];

那么一起来:

var tokens = [[..my tokens..].sort().join("|").replace(/</g,"&lt;")]; 

(顺便说一句,我确实测试了'"', '"s'or之类的标记'span',它们似乎工作正常。另外,我不确定为什么.sort()在这里很重要,但我把它留在了,因为我喜欢靠近原始代码。)

于 2013-05-13T20:50:36.437 回答