5

I'm looking for a way to search a node's innerText for a matching string. Then wrap the matching text in a <span>.

Requirements:

1 > The node may contain html tags and I would like those tags to be maintained within the matching text selection.

2 > I would also like this to be case sensitive on the matching text.

Ex:

This is my <strong>content</strong> to search.

I would like to find "my content to search"

Match and wrap "my <strong>content</strong> to search" in a span resulting in:

This is <span>my <strong>content</strong> to search</span>.

This code only searches for an exact match within a node and doesn't match if there's html tags (because those are additional child nodes):

/* Prompt for search */
    var text = prompt("Search for:", "");
    if (text == null || text.length == 0) return;

    /* Remove highlighted results */
    var spans = document.getElementsByClassName("labnol");

    if (spans) {
        for (var i = 0; i < spans.length; i++) {
            spans[i].style.backgroundColor = "transparent";
        }
    }

    function searchWithinNode(node, te, len) {
        var pos, skip, spannode, middlebit, endbit, middleclone;

        skip = 0;
        if (node.nodeType == 3) {
            pos = node.data.indexOf(te);

            if (pos >= 0) {
                spannode = document.createElement("span");
                spannode.setAttribute("class", "labnol");
                spannode.style.backgroundColor = "yellow";
                middlebit = node.splitText(pos);
                endbit = middlebit.splitText(len);
                middleclone = middlebit.cloneNode(true);
                spannode.appendChild(middleclone);
                middlebit.parentNode.replaceChild(spannode, middlebit);
                skip = 1;
            }
        } else if (node.nodeType == 1 && node.childNodes && node.tagName.toUpperCase() != "SCRIPT" && node.tagName.toUpperCase != "STYLE") {
            for (var child = 0; child < node.childNodes.length; ++child) {
                child = child + searchWithinNode(node.childNodes[child], te, len);
            }
        }
        return skip;
    }
    searchWithinNode(document.body, text, text.length);
jay
  • 434
  • 1
  • 5
  • 25

1 Answers1

1

I had to write code for similar purpose - colorize selected text. I have no idea how to do this throug nodes, so I do this task as a replacement of the HTML (of course if there is an special functionality in the selected text e.g. mouse over etc., it will be lose).

You can change the searchInHtml() function for your own purposes:

function colorizeSelection(color) {
    var selectedText = getSelectionText();
    if (selectedText == '') {
        console.log('Nothing is selected.');
        return;
    }

    var q = window.getSelection();

    var selectionObject;
    if (q.anchorNode.innerHTML) {
        selectionObject = q.anchorNode;
    }
    else {
        selectionObject = q.anchorNode.parentNode;
    }

    if (color != undefined) {
        var tmp = selectionObject.innerHTML;
        tmp = tmp.replace(
            new RegExp('(' + selectedText.replace(/\(/g,'\\(').replace(/\)/g,'\\)').replace(/\//g,'\\/').replace(/\:/g,'\\:') + ')'),
            '<span class="colorized" style="background-color: ' + color +'">$1</span>'
        );
       
        if (tmp.length != selectionObject.innerHTML.length) {
            selectionObject.innerHTML = tmp;
        }
        else {
            console.log('Possible tags in the selection.');
            var matchingStr = searchInHtml(selectionObject.innerHTML, selectedText);
            selectionObject.innerHTML = selectionObject.innerHTML.replace(matchingStr,
                '<span class="colorized" style="background-color: ' + color +'">' + matchingStr + '</span>');
        }
    }
    else {
        // selectionObject.parentNode.innerHTML = selectionObject.parentNode.innerHTML.replace(
        //     new RegExp('<span class="colorized" style="background-color: .*?">(.*?)</span>'),
        //     '$1'
        // );
        q.baseNode.parentNode.style.backgroundColor = null;
    }

    function searchInHtml(html, text) {
        var splitted = splitHtml(html);
        var resultStr = '';

        var textOnly = '';
        for (var i = 0; i < splitted.length; i++) {
            if (!splitted[i].startsWith('<')) {
                textOnly += splitted[i];
            }
        }

        var start = textOnly.indexOf(text);

        if (start >= 0) {
            var startTmp = 0;
            var length = 0;

            for (var i = 0; i < splitted.length; i++) {
                if (splitted[i].startsWith('<')) {
                    if (resultStr == '') {
                        continue;
                    }
                    else {
                        resultStr += splitted[i];
                    }
                }
                else {
                    if (resultStr.length == 0) {
                        startTmp += splitted[i].length;
                        if (start >= startTmp) {
                            continue;
                        }
                        var substrLength = Math.min(startTmp - start, text.length);
                        resultStr = text.substr(0, substrLength);
                        length = substrLength;
                    }
                    else {                    
                        var substrLength = Math.min(splitted[i].length, text.length - length);
                        resultStr += splitted[i].substr(0, substrLength);
                        length += substrLength;
                    }
                }

                if (length == text.length) {
                    break;
                }
            }
        }

        return resultStr;

        function splitHtml(html) {
            var splitted = [];

            while (html.length > 0) {
                var toPosition = 0;
                if (html[0] == '<') {
                    toPosition = html.indexOf('>');
                }
                else {
                    toPosition = html.indexOf('<') - 1;
                }

                if (toPosition <= -1) {
                    toPosition = html.length;
                }

                splitted.push(html.substring(0, toPosition + 1));
                html = html.substr(toPosition + 1);
            }

            return splitted;
        }
    }

    function getSelectionText() {
        var text = "";
        if (window.getSelection) {
            text = window.getSelection().toString();
        } else if (document.selection && document.selection.type != "Control") {
            text = document.selection.createRange().text;
        }
        return text;
    }
}