【问题标题】:Javascript text highlight functionJavascript文本高亮功能
【发布时间】:2013-05-07 14:55:01
【问题描述】:

场景

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

它基于递归替换,将&lt;span&gt; 标签包裹在找到的匹配项周围。

JsFiddle demo.

问题

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

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

注意在实际应用中允许单字符令牌。

问题

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

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

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

【问题讨论】:

  • 这并不像您正在做的那么简单,您已经证明了这一点。我不认为 jQuery 是解决这个问题的途径。您需要获取一个元素,递归搜索其后代节点,并为任何文本节点,将其替换为新的跨度节点
  • 你认为为什么需要使用 jQuery?这对某些事情很好,但我不会说这是一个。我的意思是,您可以使用 jQuery 最初选择要在其中突出显示的元素,但我不会使用 jQuery 进行实际操作
  • @Ian。我认为在这种情况下不需要 jQuery。我的意思是,该算法根本不使用 jQuey,我什至没有在问题的标签列表中添加 jQuery。这是一个纯 Javascript 解决方案。虽然我最终使用 jQuery 与 DOM 交互。
  • 啊,好吧,我只是很困惑为什么你问“我在哪里使用 jQuery”(或者任何问题)。我发誓我最近回答了一个类似的问题,所以让我看看能不能找到我的答案
  • 我刚刚发布了一个答案。我意识到,当我之前尝试做这样的事情时,当我去发布答案时,已经有人想出来了,所以我放弃了,因为我无法让它发挥作用。所以我自己来创造这一切,因为这种操纵引起了我的兴趣,而且我对自己的所作所为有点过火了。尽管如此,我想用它制作一个迷你库并维护它,在任何地方进行改进并尽可能添加,所以这就是我开发它的原因。希望对您有所帮助,如果您有任何问题,请告诉我

标签: javascript algorithm substring highlight


【解决方案1】:

我认为处理这个问题的方法是遍历一个元素的所有后代,检查它是否是一个文本节点,然后用 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
        }]
    });
};

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

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

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

参考资料:

【讨论】:

    【解决方案2】:

    这似乎对我有用:

    JsFiddle demo 中的第 17 行)

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

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

    大家一起,那么:

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

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

    【讨论】:

      猜你喜欢
      • 2012-01-23
      • 2015-05-13
      • 1970-01-01
      • 2012-09-21
      • 2017-05-26
      • 2012-06-23
      • 2015-02-01
      • 2018-02-23
      • 2017-08-20
      相关资源
      最近更新 更多