JavaScript:查找(和替换)不在特定HTML元素中的文本?

时间:2015-05-14 14:30:01

标签: javascript jquery regex

TL; DR;摘要

如何注入< span>围绕当前页面的HTML中找到的特定单词或短语忽略我想要注入的同一范围内包含 ALREADY 的任何文字。

由于处理了大量值,因此必须具有高性能!

示例:

搜索“foo”

应找到匹配项:

< p>这句话包含一个foo bar值< / p>

应该找到匹配项:

< p>这句话包含< span class ='widget'> foo bar< / span>值LT; / p为H.

背景 - 即为什么?

我正在研究一个特定的问题,即必须注入一个< span class ='widget'>动态地在页面上找到的特定文本周围的元素。我正在寻找的文字是一个很大的数组。

  • 要查找的文本字符串数组有数千个
  • 文字值可以包含短语或单词
  • 短语必须优先于单词

这最后一个是杀手。 例如:

  • 我有两个值“foo bar”和“foo”
  • 我想处理这句话:“这是一个foo bar句子”

我完成处理后......

所需输出

“这是一个< span class ='widget'> foo bar< / span>句子”

不想要

“这是一个< span class ='widget'> foo< span class ='widget'> bar< / span>< / span>句子”

现在......实现这一目标的第一步是按长度对数组进行排序(首先处理最长的数组)。但问题是,在处理完后,我的find-replace逻辑仍然在(已处理的)短语中找到较小的“单词”。

2 个答案:

答案 0 :(得分:1)

当且仅当没有嵌套的<span> - 标签时,您可以搜索

/(<span\b[^>]*>[\s\S]*?<\/span>)|(\b(?:foo|bar)(?:\s+(?:foo|bar))*)/g并将其替换为函数

function matchEvaluator(_, span, word) {
    if (span) return span;
    return '<span class="widget">' + word + '</span>';
}
  • 部分(<span\b[^>]*>[\s\S]*?<\/span>)搜索span元素 那是不允许嵌套<span> - 元素的部分。匹配的文本返回不变(匹配它们的原因是消耗<span>内的所有字符)
  • <span\b[^>]*>搜索开始标记 - 这可能不足以满足您的需求。也许你会尝试更具体,例如像<span\b(?:\s+\w[\w-]*(?:=(?:"[^"]*"|'[^']*'|\S*)))*>
  • 之类的东西
  • (\b(?:foo|bar)(?:\s+(?:foo|bar))*)搜索单词&#34; foo&#34;和&#34; bar&#34;
    如果有的话,它会搜索空格字符和另一个&#34; foo&#34; 或&#34; bar&#34; (反复)。 由于<span> - 标签及其所有内容已被消费,因此您只能匹配&#34; foo&#34;和&#34; bar&#34;在<span>
  • 之外
  • matchEvaluator-function测试,如果匹配span元素,如果匹配,则只返回匹配的文本。否则,单词将被匹配,并将它们返回包装到新的span ..

测试:

var texts = [
    "This is a foo bar sentence",
    "This sentence contains a <span class='widget'>foo bar</span> value"
];

var wordsOutsideSpan_rx = /(<span\b[^>]*>[\s\S]*?<\/span>)|(\b(?:foo|bar)(?:\s+(?:foo|bar))*)/g;
function wrapInSpan(_, span, word) {
    if (span) return span;
    return '<span class="widget">' + word + '</span>';
}

texts.forEach(function (txt) {
     console.log(txt.replace(wordsOutsideSpan_rx, wrapInSpan));
});

// outputs
// "This is a <span class="widget">foo bar</span> sentence"
// "This sentence contains a <span class='widget'>foo bar</span> value"

答案 1 :(得分:0)

好的,这是另一种方式。

我使用jQuery来查找元素(不是真的需要,但它很方便)。 此解决方案接受嵌套的<span>,并且可能更快。请分享您的结果。

(function () {
    var testwords_rx = /\b(?:foo|bar)\b/; // it's annoying, but should be faster
    var words_rx = /\b(?:foo|bar)\b(?:\s+(?:foo|bar)\b)?/g;

    function filterTextElement(idx, element) {
        return element != null &&
               element.nodeType == 3 && // #text node
               element.nodeValue.match(testwords_rx); // find at least one match
    }

    function wrapFoobars(idx, element) {
        var lastPos = 0;
        var text = element.nodeValue;
        var parent = element.parentNode;

        function addUnwrapped(start, end) {
            var textNode = document.createTextNode(text.substring(start, end));
            parent.insertBefore(textNode, element);
        }

        function addWrapped(start, end) {
            var span = document.createElement('span');
            span.className = 'widget';
            span.style.border = "1px solid red";

            var txtprop = 'textContent' in span ? 'textContent' : 'innerText';
            span[txtprop] = text.substring(start, end);

            parent.insertBefore(span, element);
        }

        function splitAndWrapText(words, pos) {
            if (pos > lastPos) {
                addUnwrapped(lastPos, pos);
            }

            lastPos = pos + words.length;
            addWrapped(pos, lastPos);
        }

        text.replace(words_rx, splitAndWrapText);
        if (lastPos < text.length) {
            addUnwrapped(lastPos, text.length);
        }

        parent.removeChild(element);
    }

    $('body *')
        .filter(':not(.widget, .widget *)')
        .contents()
        .filter(filterTextElement)
        .each(wrapFoobars)
    ;
})();

它是如何运作的

  • $('body *').filter(':not(.widget, .widget *)')

    选择<body>中的所有代码并过滤掉.widget - 元素及其所有后代(将其更改为仅选择您需要的元素)

  • .contents()

    获取匹配元素的所有子元素(包括文本节点)

  • .filter(filterTextElement)

    过滤以仅获取至少包含其中一个搜索字词的#text元素

  • wrapFoobars: 替换比赛。必须将第一个之间,最后一个匹配之间和之后的文本作为文本节点(addUnwrapped)插入,匹配的文本本身将包装到新创建的<span> - 元素(addWrapped )。 最后,删除原始文本元素(parent.removeChild(element);