Search code examples
javascriptjqueryregexregexp-replace

Javascript RegExp replace text between two tags if the opening tag has specified class


I have the following HTML code:

<span class="whatever-class custom-class-name" attribute="Whatever 1 AAA">AAA BBB</span>
<span class="search-text custom-class-name" attribute="Whatever 2 AAA">Text AAA</span>

I want to replace the text between the span tags, but only if the span tag has the class "search-text". So in my case, I have a string containing an HTML code with two spans. I want to replace the text from the second span if it contains a searched text.

I search for: "aa" and I want to replace it with <span class="highlight-text">aa</span>. So the final result should be:

<span class="whatever-class custom-class-name" attribute="Whatever 1 AAA">AAA BBB</span>
<span class="search-text custom-class-name" attribute="Whatever 2 AAA">Text <span class="highlight-search">AA</span>A</span>

Right now I'm doing something like:

var paint = $.proxy(this._paint, this);
var regex = /(<span class="search-text[^>]+>|<\/span>)/g;
item.node.innerHTML = item.html.replace(regex, paint);

where "value" is: "aa" and "item.html" is the HTML presented at the beginning of my question.

the _paint function:

_paint: function($0) {
   return '<span class="highlight-text">' + $0 + '</span>';
},

At this moment the result is that the second span is entirely wrapped into the '<span class="highlight-text">' + $0 + '</span>'; . This is the result:

<span class="whatever-class custom-class-name" attribute="Whatever 1 AAA">AAA BBB</span>
<span class="highlight-text"><span class="search-text custom-class-name" attribute="Whatever 2 AAA">Text AAA</span></span>

I want only the text match to be wrapped inside the hghlight span, like this:

<span class="whatever-class custom-class-name" attribute="Whatever 1 AAA">AAA BBB</span>
<span class="search-text custom-class-name" attribute="Whatever 2 AAA">Text <span class="highlight-text">AA</span>A</span>

Any ideas? Thanks.


Solution

  • Regex is notoriously the wrong tool for most of this job; it's designed for manipulating strings, not structured data such as HTML. Fortunately, you're already in the browser, so you have an entire toolset designed for DOM manipulation available: may as well use it. (You've also tagged the question with jQuery, which makes it even easier.)

    Update: I'd misread a detail in the question, and was pulling the search string from a parent node's attribute instead of externally; I also failed to make the search case-insensitive. Both now corrected in the below:

    // Make a case-insensitive regex from the search string
    let str = 'aa';
    let re = new RegExp(str, "gi");
    
    // operate only on the .search-text nodes:
    $('.search-text').each(function(i, el) {
      // get the current contents of the element:
      let text = $(el).html();
    
      // Add your highlights:
      text = text.replace(re, '<span class="highlight-text">$&</span>');
    
      // insert the modified text back into the DOM:
      $(el).html(text);
    })
    .highlight-text {
      background-color: #FFC
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <span class="whatever-class custom-class-name" attribute="Whatever 1 AAA">AAA BBB</span>
    <span class="search-text custom-class-name" attribute="Whatever 2 AAA">Text AAA</span> I

    This is only really safe if the .search-text elements have no child nodes. It will generally work even if they contain some HTML, but only if:

    • You're certain that the strings you're highlighting will never match portions of the HTML itself, and
    • there aren't any event bindings attached to the DOM elements (this script replaces the contents of .search-text wholesale.)

    For example, trying to highlight the word "span" in an html string containing <span> elements would result in invalid html:

    // same script as above
    $('.search-text').each(function(i, el) {
      let text = $(el).html();
      let highlights = $(el).attr("attribute").split(" ");
      for (str of highlights) {
        text = text.replace(str, '<span class="highlight-text">' + str + '</span>');
      }
      $(el).html(text);
    })
    .highlight-text {
      background-color: #FFC
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <span class="search-text" attribute="span">Text AAA <span>test</span></span>

    Starting with a string

    If your starting point is an HTML string instead of an already-built DOM tree, all you need to do is convert that string into a document fragment first so you can use these DOM tools on it:

    let fragment = $('<template>');
    fragment.html($yourStringHere);
    /* manipulate fragment contents as above, then */
    return fragment.html();