Thookes
Thookes

Reputation: 99

Seach words given in HTML textarea, and highlight them in a string with JavaScript

I need to search in a string for some given keywords, then highlight them (for example write them with red letters). I've tried in many different ways to resolve this problem, but with no result. The most logical way would be, I think, to make an array with the keywords, then search for substrings and put the found substrings between span tags, but I have a problem here: if the given keywords are aaa and aab, the string aaab should be all red. But after I find the aaa substring, and put them between span tags, I'm unable to find aab and highlight it, while keeping the previous substring's color, since if I search in the original string and highlight the new keyword, the span tags of first keyword will be lost, or if I search in the new string, the last two a will be separated from b with a span end-tag. Is there any way, to do this, for example ignore HTML tags while searching for keywords, or something else?

Upvotes: 1

Views: 361

Answers (1)

Brigand
Brigand

Reputation: 86220

You need to create an array of offset/length pairs. For example with "foo aaab bar aaa", you'd get this array:

[
  {offset: 4, length: 3, keyword: "aaa"},
  {offset: 5, length: 3, keyword: "aab"},
  {offset: 12, length: 3, keyword: "aaa"}
]

You then need to go through this array and flatten overlapping sections, resulting in:

[
  {offset: 4, length: 4, keywords: ["aaa", "aab"]},
  {offset: 12, length: 3, keywords: ["aaa"]}
]

With this, you have enough information to place the span tags correctly at the various offsets and lengths.

Flattening the offsets is the most difficult part, here's my attempt at it, but I haven't fully tested it. Play with the jsfiddle

function flattenOffsets(xs){
    var out = [];
    var alreadyProcessed = [];

    xs.forEach(function(x, i){
        if (alreadyProcessed.indexOf(x) !== -1) {
            return;
        }

        var xStart = x.offset, xEnd = x.offset+x.length;
        var fixed = {offset: x.offset, length: x.length, keywords: [x.keyword]};
        var matches = xs.slice(i+1).filter(function(y){
            var yStart = y.offset, yEnd = y.offset+y.length;

            var overlapBefore = xStart <= yStart && xEnd <= yEnd && xEnd >= yStart;
            var overlapAfter = yStart <= xStart && yEnd <= xEnd && yEnd >= xStart;
            var contains = yStart >= xStart && yEnd <= xEnd;
            var contained = xStart >= yStart && xEnd <= yEnd;    

            return overlapBefore || overlapAfter || contains || contained;
        }).sort(function(a,b){
            return b.offset - a.offset;
        })
        .forEach(function(y){
            fixed.offset = Math.min(x.offset, y.offset);
            fixed.length = Math.max((x.offset + x.length), (y.offset + y.length)) - fixed.offset;
            fixed.keywords.push(y.keyword);
            alreadyProcessed.push(y);
        });

        out.push(fixed);
        alreadyProcessed.push(x);
    });

    return out;
}

Upvotes: 1

Related Questions