【问题标题】:Find word in HTML在 HTML 中查找单词
【发布时间】:2011-09-11 18:01:46
【问题描述】:

我正在尝试在 HTML 字符串中查找给定的单词并在其周围添加一个跨度。

我现在做的是这样的:

function find(what:String,where:String)
{
    var regexp:RegExp=new RegExp(what,'gi');
    return where.replace(regexp,'<span>$&</span>');
}

它适用于不在 HTML 标记内的单词。 我想要的是忽略 HTML 标签内的那些。

示例: find("西班牙")
输入:

The rain in <b class="spain">Spain</b> stays mainly in the <i data-test="Spain">plain</i>.


输出:

The rain in <b class="spain"><span>Spain</span></b> stays mainly in the <i data-test="Spain">plain</i>.

请问我怎样才能做到这一点?

【问题讨论】:

  • 为什么会得出需要正则表达式的结论?您提出问题,我们会解决问题。
  • 你可以访问 DOM 吗?如果您不必自己解析 HTML,那就容易多了。
  • 我将它作为一系列字符串连接起来输出。

标签: javascript regex


【解决方案1】:

要考虑可能匹配的 html 标记和属性,您将需要以一种或另一种方式解析该 HTML。最简单的方法是将其添加到 DOM(或只是添加到新元素):

var container = document.createElement("div");
container.style.display = "none";
document.body.appendChild(container);  // this step is optional
container.innerHTML = where;

解析后,您现在可以使用 DOM 方法迭代节点并仅找到文本节点并在这些节点上进行搜索。使用递归函数遍历节点:

function wrapWord(el, word)
{
    var expr = new RegExp(word, "i");
    var nodes = [].slice.call(el.childNodes, 0);
    for (var i = 0; i < nodes.length; i++)
    {
        var node = nodes[i];
        if (node.nodeType == 3) // textNode
        {
            var matches = node.nodeValue.match(expr);
            if (matches)
            {
                var parts = node.nodeValue.split(expr);
                for (var n = 0; n < parts.length; n++)
                {
                    if (n)
                    {
                        var span = el.insertBefore(document.createElement("span"), node);
                        span.appendChild(document.createTextNode(matches[n - 1]));
                    }
                    if (parts[n])
                    {
                        el.insertBefore(document.createTextNode(parts[n]), node);
                    }
                }
                el.removeChild(node);
            }
        }
        else
        {
            wrapWord(node, word);
        }
    }
}

这是一个工作演示:http://jsfiddle.net/gilly3/J8JJm/3

【讨论】:

  • 你好,吉莉。谢谢。看起来不错,我遇到的唯一问题是,一旦有了我想要的 HTML 字符串,我必须用更改后的字符串替换正文中的所有内容(作为我要搜索的正文内容)。跨度>
  • gilly,小心,如果你在里面扔了另一个“西班牙”,看看你的例子会发生什么:jsfiddle.net/vol7ron/J8JJm/1
  • 哎呀!等我回到电脑前看看。
  • 这个问题似乎比我最初想象的要难——哦,这些问题很有趣
  • @Francisc - 完全不用担心 HTML 字符串,只需将 document.body 传递给递归函数即可。例如,致电wrapWord(document.body, "spain")。文本将被替换。
【解决方案2】:

您将无法使用正则表达式以任何可靠的方式处理 HTML。相反,将 HTML 解析为 DOM 树并迭代 Text 节点检查其数据的内容。

如果您在 Web 浏览器中使用 JavaScript,则已经为您完成了解析。请参阅this question,例如 wrap-word-in-span 代码。如果您需要匹配可能分散在不同元素中的短语,那就更麻烦了。

【讨论】:

  • 谢谢。无论如何要替换页面内容还是我必须获取正文元素,将其传递给函数然后用解析的字符串替换所有内容,这似乎很糟糕。
  • 链接答案中的示例代码将匹配的内容替换为该匹配的包装版本。如果需要,您可以通过更改传递给findTextfunction() { ... } 中的代码来替换为其他内容,例如使用replaceChild 而不是insertBefore。确实,您不想接触 HTML 字符串。
  • 谢谢你,博宾斯。给我来一杯柠檬水。
【解决方案3】:
function find(what:String,where:String)
{
    what = what.replace(/(\[|\\|\^|\$|\.|\||\?|\*|\+|\(|\)|\{|\})/g, "\\$1")
          .replace(/[^a-zA-Z0-9\s:;'"~[\]\{\}\-_+=(),.<>*\/!@#$%^&|\\?]/g, "(?:&[0-9A-Za-z]{3,25};|&#[0-9]{1,10};?|[^\s<])")
          .replace(/</g,"&lt;?").replace(/>/g,"&gt;?").replace(/"/g,"(?:\"|&quot;?)")
          .replace(/\s/g, "(?:\\s|&nbsp;?)");

    what = "(>[^<]*|^[^<]*)(" + what + ")";
    var regexp:RegExp=new RegExp(what,'gi');
    return where.replace(regexp,'$1<span>$2</span>');
}
  1. 第一个替换函数在 RE 中具有特殊含义的字符前添加反斜杠,以防止错误或意外结果。
  2. 第二个替换函数将搜索查询中出现的每个未知字符替换为(?:&amp;[0-9A-Za-z]{3,25};|&amp;#[0-9]{1,10};?|[^\s&lt;])。这个 RE 由三部分组成:首先,它尝试匹配一个 HTML 实体。其次,它尝试匹配 HTML 数字实体。最后,它匹配任何非空白字符(以防 HTML 文档的创建者未正确编码字符)。
  3. 第三、第四和第五个替换函数将&lt;&gt;"替换为对应的HTML实体,这样搜索查询就不会通过标签进行搜索。
  4. 第六个替换函数用 RE (\s|&amp;nbsp;?) 替换空格,匹配空格字符和 HTML 实体。

这个函数唯一的缺点是未记录的特殊字符(例如)匹配任何HTML实体/字符(下面的例子,不仅&amp;euro;和是有效匹配,而且@987654330 @ 和 @)。

这个建议的解决方案适用于大多数情况。在复杂的情况下它可能不准确,这可能并不比 DOM 迭代差(它很容易受到内存泄漏的影响,并且需要更多的计算能力)。

当您使用通过 DOM 分配了事件侦听器的 HTML 元素时,您应该遍历所有(子)元素,并将此函数应用于每个 Text 节点。

【讨论】:

  • 嗯...我不这么认为。我正在考虑遍历结果并检查&lt; 的首次出现位置到&gt; 的首次出现位置。我会试一试。谢谢。
  • 不过,这很容易坏掉。
  • @Francisc 我改进了我的答案。在大多数情况下,新功能就足够了。
  • 谢谢,罗伯。你能详细说明一下每个替换的作用和原因吗?
  • @Francisc 在答案中添加了详细说明。
【解决方案4】:
  • 纯 JavaScript(基于 jQuery 的 Sizzle.getText); Demo: http://jsfiddle.net/vol7ron/U8LLv/

    var wrapText = function ( elems,regex ) {
        var re = new RegExp(regex);
        var elem;
    
        for ( var i = 0; elems[i]; i++ ) {
            elem = elems[i];
    
            // Get the text from text nodes and CDATA nodes
            if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
                parent = elem.parentNode;
                re.lastIndex = 0;
                if(re.test(elem.nodeValue)){               
                    var span = document.createElement('span');
                    span.innerHTML = RegExp.$1;
    
                    if (RegExp.leftContext != ''){
                       parent.insertBefore(document.createTextNode(RegExp.leftContext),elem);    i++;
                    }
    
                    parent.insertBefore(span,elem);   i++;
    
                    if (RegExp.rightContext != ''){
                       parent.insertBefore(document.createTextNode(RegExp.rightContext),elem);   i++;
                    }
    
                    parent.removeChild(elem);
                }                   
    
            // Traverse everything else, except comment nodes
            } else if ( elem.nodeType !== 8 ) {
                wrapText( elem.childNodes, regex );
            }
        }
    
        return;
    };
    
    
    var obj = document.getElementById('wrapper');
    wrapText([obj],/(spain)/gi);
    

【讨论】:

  • 谢谢,但我不能使用 jQuery。
  • @vol7ron:如果我是 OP,我可能会回答“我不需要更多;我需要对所述问题的答案。”
  • 在文本节点上设置elem.nodeValue 会将尖括号转换为&amp;lt;&amp;gt;。您将更改文本,而不是 html。
猜你喜欢
  • 2014-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多