【问题标题】:jquery replace matching text with htmljquery用html替换匹配的文本
【发布时间】:2016-01-05 23:41:21
【问题描述】:

我想使用 jQuery 动态地将所有与给定正则表达式匹配的文本与特定标记相结合。例如:

<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 并且我想用 &lt;span class='meat-product'&gt; 括起来,那么我想转换为

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

但前者用文本替换文本(所以我得到文本 &lt;span ... 而不是 &lt;span&gt; 元素),后者匹配任何 HTML 内的 hamspam 而不仅仅是文本。

我怎样才能只匹配文本,又能用 HTML 元素替换该文本?

【问题讨论】:

  • 建议不要尝试重新发明轮子并使用经过良好测试的荧光笔插件为您完成此操作。您缺少的是使用contents() 来隔离文本节点与
  • 不会像此解决方案中那样查找和替换工作吗? stackoverflow.com/questions/21017744/…
  • @NateSnyder no...这会影响匹配的类和属性,并可能破坏 html、url 等
  • 我想这就是你要找的东西:stackoverflow.com/questions/26951003/…
  • @NateSnyder 你是对的,它或多或少看起来像是重复的,但这个问题确实没有任何好的答案。接受的一个使用正则表达式来解析HTML,即fraught with peril,另一个答案只是一个模糊的建议。

标签: jquery html


【解决方案1】:

我怎样才能只匹配文本,又能用 HTML 元素替换该文本?

您可以选择所有后代元素和文本节点并将集合过滤为仅包含文本节点。然后你可以简单地用修改后的字符串替换每个文本节点:

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

$(".content *").contents().filter(function() {
  return this.nodeType === 3;
}).replaceWith(function() {
  return this.textContent.replace(/(ham|spam)/g, '<span class="meat-product">$1</span>');
});
.meat-product {
  color: #f00;
}
<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>
  • 使用通用选择器* 选择所有后代元素(当然您也可以使用$('.content').children(),但这只会深入一层...您可能会遇到嵌套元素,这意味着您将需要检索所有文本节点,因此我使用*来选择所有后代元素的内容)。
  • .contents() method 获取所有文本节点和子元素。
  • 然后过滤集合,以便只包含文本节点,因为我们不想替换任何 HTML。文本节点的nodeType 属性值为3。
  • 最后,.replaceWith() method 用于用替换的文本字符串更新每个文本节点。 .replaceWith() 方法的好处是您可以将文本替换为 HTML 字符串。
  • 您还可以使用上面示例中的捕获组来缩短您的.replace() 方法。然后,您可以在 span 元素之间简单地替换 $1

【讨论】:

  • 应该自己也想用过滤器+1
  • +1,这几乎行得通,我最终自己做了这样的事情(不那么优雅),而且一团糟。你必须转义所有文本,不管它是否匹配,这样如果文本包括&amp;lt;&amp;gt;&amp;amp;,它们就会被更改为&amp;lt;&amp;gt;&amp;amp;,这不是什么如果您穿插 HTML 代码,则可以自动化。
【解决方案2】:

这里有一个解决方案,涵盖了所示的琐碎案例,仅用于展示如何使用 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>';
    })
}

正如 cmets 中提到的,有一些经过实战考验的插件可以为您做到这一点

DEMO

【讨论】:

    【解决方案3】:

    我最终改用了 Josh Crozier 的解决方案。问题是如果文本中有&amp; &lt; &gt; 字符,如果它们被用作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);
        }
    });
    

    【讨论】:

      【解决方案4】:

      我很欣赏 OP 明确说明

      我想用jQuery

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

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

      1. 确定哪些元素具有文本节点(与正则表达式匹配)直接childNodes
      2. 使用原始元素作为模板,克隆每个元素并从空中重建它们;
      3. 删除每个原始元素,留下新建的克隆

      /* ### 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>

      【讨论】:

      • +1 -- jQuery-o-phobe 刚问世时我就开始了,我很欣赏纯 JS 的使用。在这种情况下,我已经将 jQuery 用于其他事情,所以如果它更容易的话对我来说是有意义的。
      猜你喜欢
      • 2012-05-01
      • 2013-04-16
      • 2023-01-12
      • 2011-06-20
      • 1970-01-01
      • 2016-06-19
      • 2011-06-04
      • 1970-01-01
      • 2015-06-24
      相关资源
      最近更新 更多