jquery用html替换匹配的文本

时间:2016-01-04 19:04:56

标签: jquery html

我想使用jQuery动态地将与给定regexp匹配的所有文本与特定标记一起包含在内。例如:

<div class="content">
<div class="spamcontainer">
Eggs and spam is better than ham and spam.
<img src="spam-and-eggs.jpg">
I do not like green eggs and spam, though.
</div>
</div>

如果我的正则表达式是ham|spam而我想用<span class='meat-product'>括起来,那么我想转换为

<div class="content">
<div class="spamcontainer">
Eggs and <span class='meat-product'>spam</span> is better than <span class='meat-product'>ham</span> and <span class='meat-product'>spam</span>.
<img src="spam-and-eggs.jpg">
I do not like green eggs and <span class='meat-product'>spam</span>, though.
</div>
</div>

这是我的问题。我知道如何只使用文本,只使用html:

$("div.content").text(function() {
    return $(this).text().replace(/ham|spam/, function(match) {
        return '<span class="meat-product">' + match + '</span>';
        });
});

$("div.content").html(function(_, html) {
    return html.replace(/ham|spam/, function(match) {
        return '<span class="meat-product">' + match + '</span>';
        });
});

但前者用文本替换文本(因此我得到文本<span ...而不是<span>元素),后者匹配任何HTML中的hamspam而不仅仅是文本。

如何仅匹配文本,还能用HTML元素替换该文本?

4 个答案:

答案 0 :(得分:2)

  

如何仅匹配文本,还能用HTML元素替换该文本?

您可以选择所有后代元素和文本节点,并将该集过滤为仅包含文本节点。然后,您只需使用修改后的字符串替换每个文本节点:

$(".content *").contents().filter(function() {
  return this.nodeType === 3;
}).replaceWith(function () {
  return this.textContent.replace(/(ham|spam)/g, '<span class="meat-product">$1</span>');
});

&#13;
&#13;
$(".content *").contents().filter(function() {
  return this.nodeType === 3;
}).replaceWith(function() {
  return this.textContent.replace(/(ham|spam)/g, '<span class="meat-product">$1</span>');
});
&#13;
.meat-product {
  color: #f00;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="content">
  <div class="spamcontainer">
    Eggs and spam is better than ham and spam.
    <img src="spam-and-eggs.jpg">I do not like green eggs and spam, though.
  </div>
</div>
&#13;
&#13;
&#13;

  • 使用通用选择器*来选择所有后代元素(当然你也可以使用$('.content').children(),但这只会深入一级......你可能会遇到嵌套元素表示您需要检索所有文本节点,因此我使用*选择所有后代元素的内容。)
  • .contents() method获取所有文本节点和子元素。
  • 然后过滤该集,以便只包含文本节点,因为我们不想替换任何HTML。文本节点的nodeType属性值为3。
  • 最后,.replaceWith() method用于使用替换的文本字符串更新每个文本节点。 .replaceWith()方法的好处是可以用HTML字符串替换文本。
  • 您还可以使用上述示例中的捕获组来缩短.replace()方法。然后,您只需在$1元素之间替换span

答案 1 :(得分:1)

这是一个解决方案,涵盖了所显示的简单案例,仅用于展示如何使用jQuery遍历文本节点。如果你必须处理更深的嵌套

,它将需要一些递归的DOM行走
var reg = /ham|spam/g;

$('.content').children().contents().each(function() {
    if (this.nodeType === 3) {
        var txt = this.textContent;
        if (txt.match(reg)) {
            $(this).replaceWith(textToHtml(this));
        }
    } 
});


function textToHtml(node) {
    return node.textContent.replace(reg, function(match) {
        return '<span class="meat-product">' + match + '</span>';
    })
}

正如评论中所提到的,有战斗测试的插件可以为你做这个

DEMO

答案 2 :(得分:1)

我最终使用了Josh Crozier的解决方案。问题是如果文本中有& < >个字符,如果它们被用作replaceWith()的HTML参数,则需要对它们进行转义。如果您有意插入HTML,它们将无法被普遍转义。所以我做了这样的事情:

function escapeHTML(s) {
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
$("div.content *").contents().filter(function() {
    return this.nodeType === 3;
}).each(function() {
    var node = $(this);
    var text = node.text();
    var occurrences = 0;
    var html = text.replace(/(ham|spam)|.+?/g, function(match, p1) {                
            if (p1)
            {   
                // we found ham or spam
                ++occurrences;
                return '<span class="meat-product">'
                    + escapeHTML(match)    // technically not necessary 
                                 // since we know "ham" and "spam"
                                 // don't need escaping, but some regex's
                                 // need this
                    + '</span>';
            }
            else
            {
                return escapeHTML(match);
            }
        });
    if (occurrences > 0)
    {
        node.replaceWith(html);
    }
});

答案 3 :(得分:1)

我很欣赏OP明确指出

  

我想使用jQuery

但我想看看我是否可以构建普通的javascript解决方案。 (我还没有开始学习jQuery)。

最后,我的解决方案涉及3个步骤:

  1. 确定哪些元素的文本节点(与正则表达式匹配)为直接childNodes;
  2. 克隆每个元素并使用原始元素作为模板从空重建它们;
  3. 删除每个原始元素,留下新构建的克隆
  4. /* ### Build an array of 'matching elements' which have (as direct childNodes) text nodes containing 'spam' or 'ham' */
    
    var matchingElements = [];
    var allElements = document.querySelectorAll('*:not(script)');
    
    for (var i = 0; i < allElements.length; i++) {
    	var elementAdded = false;
        for (var j = 0; j < allElements[i].childNodes.length; j++) {
            if (allElements[i].childNodes[j].nodeValue === null) {continue;}
            if (allElements[i].childNodes[j].nodeValue.match(/ham|spam/gi) === null) {continue;}
            if (elementAdded !== true) {
            	matchingElements.push(allElements[i]);
            	elementAdded = true;
            }
        }
    }
    
    /* ### Clone each matched element and rebuild it, replacing unstructured text nodes */
    var x;
    var y;
    var newTextNode;
    var clonedElements = [];
    var meatProducts = document.getElementsByClassName('meat-product');
    
    /* Create (empty) clone of matched element */
    
    for (var k = 0; k < matchingElements.length; k++) {
            clonedElements[k] = matchingElements[k].cloneNode(false);
            matchingElements[k].parentNode.insertBefore(clonedElements[k],matchingElements[k]);
    
        for (var l = 0; l < matchingElements[k].childNodes.length; l++) {
    
    /* Add non-text nodes to cloned element */
    
            if (matchingElements[k].childNodes[l].nodeValue === null) {
                var clonedNode = matchingElements[k].childNodes[l].cloneNode(true);
                clonedElements[k].appendChild(clonedNode);
                continue;
            }
    
    /* Add text nodes which don't contain 'spam' or 'ham' to cloned element */
    
            if (matchingElements[k].childNodes[l].nodeValue.match(/ham|spam/gi) === null) {
                var clonedNode = matchingElements[k].childNodes[l].cloneNode(true);
                clonedElements[k].appendChild(clonedNode);
                continue;
            }
    
    /* Restructure and add text nodes which do contain 'spam' or 'ham' to cloned element */
    
            var meatLarder = matchingElements[k].childNodes[l].nodeValue.match(/ham|spam/gi);
            var textNodeString = matchingElements[k].childNodes[l].nodeValue;
    
            for (m = 0; m < meatLarder.length; m++) {
            x = 0;
            y = textNodeString.indexOf(meatLarder[m]);
    
            newTextNode = document.createTextNode(textNodeString.substring(x,y));
            clonedElements[k].appendChild(newTextNode);
    
            textNodeString = textNodeString.substring(y);
    
            y = meatLarder[m].length;
    
            var newSpan = document.createElement('span');
            var newMeatProduct = document.createTextNode(meatLarder[m]);
            var newClass = document.createAttribute('class');
            newClass.value = 'meat-product';
            newSpan.setAttributeNode(newClass);
            newSpan.appendChild(newMeatProduct);
            clonedElements[k].appendChild(newSpan);
    
            textNodeString = textNodeString.substring(y);
            }
    
            newTextNode = document.createTextNode(textNodeString);
            clonedElements[k].appendChild(newTextNode);
        }
    
    /* Remove original matched element from document, leaving only the newly-built cloned element */
        matchingElements[k].parentElement.removeChild(matchingElements[k]);
    }
    <div class="content">
    <div class="spamcontainer">
    Eggs and spam is better than ham and spam.
    <img src="spam-and-eggs.jpg">
    I do not like green eggs and spam, though.
    </div>
    </div>