0

EDIT

heres what i have to do...

Imagine if i have a text with some html tags inside it (it is still a string):

var string = '<p>Hello, my name is Mauricio</p><p>Hi, my name is Patricia</p><p class="warn">Yeah, My name is Carl</p><a href="#"><img src="#" /></a>';

And i want to wrap all the letters "a" with

 <span class="ui-match"></span>

but i must not replace anything from the tag, neither what is inside it, neither the class in the

element.

So if I want to wrap all the letters "a" from that string, it would return like that:

<p>Hello, my n<span class="ui-match">a</span>me is M<span class="ui-match">a</span>uricio</p><p>Hi, my n<span class="ui-match">a</span>me is P<span class="ui-match">a</span>trici<span class="ui-match">a</span></p><p class="warn">Ye<span class="ui-match">a</span>h, My n<span class="ui-match">a</span>me is C<span class="ui-match">a</span>rl</p><a href="#"><img src="#" /></a>

all the letters "a" where wrapped with

 <span class="ui-match"></span>

, but the link and the paragraph were not.

also this string is comming from a API, so its dynamic... this letter i'm searching is dynamic, so it can be "a" or "abc"... it must not be case sensitive

thanks

Mauricio Soares
  • 3,452
  • 1
  • 17
  • 16

3 Answers3

2

Does this solution matches your requirements?

string = string.replace(/a(?![^<]*?>)/g, '<span class="ui-match">a</span>');

A little help about (?![^<]*?>) (roughly : "some text not followed by >") :

(?!...)   not followed by
[^<]*     any char except "<", zero or more times
?>        until next ">"

Wrapped inside a function :

function replace(html, text, replacement) {
    // RegExp.escape : http://stackoverflow.com/q/3561493/1636522
    var re = new RegExp('(' + RegExp.escape(text) + ')(?![^<]*?>)', 'g');
    return html.replace(re, replacement);
}
var html = '<a class="azerty"> azerty &lt; azerty </a>';
html = replace(html, 'azerty', '<b>$1</b>');
// "<a class="azerty"> <b>azerty</b> &lt; <b>azerty</b> </a>"
  • 1
    Thanks for your answer @wared... but the answer from Miller Meideiros seem to be simpler, and is working great. but i really appreciate your help. Thanks! – Mauricio Soares Dec 20 '13 at 12:45
  • @MauricioSoares You're welcome :D Be careful though, your choice fails the same way. –  Dec 20 '13 at 12:49
  • @MauricioSoares Forget what I've told you previously, actually there can't be any `<` and `>` between tags since we have to replace them with HTML entities, respectively `<` and `>`. So, obviously, "your choice fails the same way"... I have to review my answer entirely ^^' Stupid me. –  Dec 20 '13 at 13:05
  • @MauricioSoares I'm reverted back to the first revision :D –  Dec 20 '13 at 13:24
  • @MauricioSoares And added a function for convenience :) –  Dec 20 '13 at 13:38
1

For not using regex, it will be faster to work with DOM nodes:

var div = document.createElement('div'),
    children;

div.innerHTML = 'Hello, my name is mauricio, and i like <a href="#">Star Wars</a>';
children = div.childNodes;

for (var i = 0, len = children.length; i < len; i++) {
    console.log(children[i]);
    if (children[i].nodeType === 3) {
        children[i].nodeValue = children[i].nodeValue.replace(/a/g, 'R');
    }
}

console.log(div.innerHTML);

N.B.: I used innerHTML property as an example way here, however it is not recommended to exploit it because of a rather low performance.

DEMO: http://jsfiddle.net/N7rdW/


UPDATE:

As per your update, you should better use the approach from my answer for another question from HERE. The code is a bit more complicated but is rather fast (not keeping in mind innerHTML usage):

var div = document.createElement('div');
div.innerHTML = 'Hello, my name is mauricio, and i like <a href="#">Star Wars</a>';

for (var i = 0, children = div.childNodes, len = children.length; i < len; i++) {
    var child = children[i];
    if (child.nodeType === 3 && child.nodeValue.indexOf('a') > -1) {
        var segments = child.nodeValue.split('a');
        for (var k = 0, lk = segments.length; k < lk; k++) {
            div.insertBefore(document.createTextNode(segments[k]), child);
            if (k < lk - 1) {
                var span = document.createElement('span');
                span.className = 'ui-match';
                span.appendChild(document.createTextNode('R'));
                div.insertBefore(span, child);
            }
        }
        div.removeChild(child);
    }
}

console.log(div.innerHTML);

DEMO: http://jsfiddle.net/T4ZXA/6/

Community
  • 1
  • 1
VisioN
  • 143,310
  • 32
  • 282
  • 281
  • Thanks Vision! i'm sorry but i formulated my question wrong... i just updated it... i dont want to replace, i want to wrap with a tag – Mauricio Soares Dec 19 '13 at 18:54
  • @MauricioSoares Then you should better use my other answer for the similar question from here: http://stackoverflow.com/a/16239900/1249581. – VisioN Dec 19 '13 at 18:56
  • hey @VisioN, do you mind creating an example that works with my string? I don't think i got your answer really well... also, is there a way to do this without creating an element? only manipulating the string? – Mauricio Soares Dec 19 '13 at 19:04
  • @MauricioSoares Please see my updated answer. FYI, you should [*never try to parse HTML with regex*](http://stackoverflow.com/a/1732454/1249581). – VisioN Dec 19 '13 at 19:09
  • Thanks, i'm testing your answer... :) – Mauricio Soares Dec 19 '13 at 19:14
  • Hey @VisioN, i tested your solution but i got some problems... i just discovered that the structured of the string is a little bit more complex... i updated the question... would u take a look please? Thanks for your patience =) – Mauricio Soares Dec 19 '13 at 20:13
1

I would recommend you to split the problem into 2 smaller problems:

  1. grab text content of all tags.
  2. wrap chars with <span class="ui-match"></span>

Using RegExp to parse HTML is a bad idea but in this case since you seem to control the input structure you might use it to simplify the logic.

Using a single RegExp for it will be really hard, so it's also better to do 2 String#replace instead of one. A generic implementation would be like:

function replaceHtmlContent(str, match, replaceFn) {
  // we use the "g" and "i" flags to make it replace all occurrences and ignore case
  var re = new RegExp(match, 'gi');
  // this RegExp will match any char sequence that doesn't contain "<" or ">"
  // and that is followed by a tag
  return str.replace(/([^<>]+)(?=<[^>]+>)/g, function(s, content){
    return content.replace(re, replaceFn);
  });
}

which could be abstracted as:

function wrapMatch(src, match) {
  return replaceHtmlContent(src, match, function(str){
    return '<span class="ui-match">'+ str +'</span>';
  });
}

and used later like:

var output = wrapMatch(input, 'a');

which would give the expected result for the example input.

DEMO: http://jsbin.com/ovUFEsas/4/edit

Community
  • 1
  • 1
Miller Medeiros
  • 1,256
  • 14
  • 12