Languages
[Edit]
EN

JavaScript - mark matching text in HTML document

5 points
Created by:
Walter
226

In this article, we want to show how to write in JavaScript simple logic that finds in indicated element (it can be the whole document) matching text and highlights it.

Marked text in the indicated HTML element - JavaScript
Marked text in the indicated HTML element - JavaScript

Note: to see the solution that groups and marks detected keywords with different markers check this article.

Below code usage example:

const element = document.querySelector('#text');
const keywords = ['text', 'underline', 'ital'];

markKeywords(element, 'yellow-marker', keywords);

The solution doesn't change document formatting - just wraps the matching test with an additional neutral element that marks matching text.

Keywords can be described as a regular expression that makes below logic very elastic.

Practical example:

// ONLINE-RUNNER:browser;

<!doctype html>
<html>
<head>
  <style>
    
    .yellow-marker {
        background: yellow;
    }
    
  </style>
</head>
<body>
  <p id="text">
    This is example text with nested <b>bold text</b>.
    <span>Other examples: <u>underline</u> or <i>italic</i></span>
  </p>
  <script>

    // Finds text nodes that contain some text.
    //
    const findNodes = node => {
        const result = [];
        const filter = /^(\s|\n)+$/i;
        const execute = node => {
            let child = node.firstChild;
            while (child) {
                switch (child.nodeType) {
                    case Node.TEXT_NODE:
                        if (!filter.test(child.data)) {
                            result.push({
                                handle: child,
                                spacers: [child.data],
                                matchings: [],
                            });
                        }
                        break;
                    case Node.ELEMENT_NODE:
                        execute(child);
                        break;
                }
                child = child.nextSibling;
            }
        }
        if (node) {
            execute(node);
        }
        return result;
    }

        // Finds next text part that matches indicated expression.
    //
    const findPart = (expression, text) => {
        const matching = expression.exec(text);
        if (matching == null) {
            return null;
        }
        const part = matching[0];
        return {
            index: matching.index,
            length: part.length,
            text: part
        };
    };

    // Finds in text parts that matches and not indicated expression.
    //
    const findParts = (expression, text) => {
        expression.lastIndex = 0;
        const spacers = [];
        const matchings = [];
      	let index = 0;
        while(true) {
            const part = findPart(expression, text);
            if (part == null) {
                break;
            }
            spacers.push(text.substring(index, part.index));
            matchings.push(part.text);
            index = part.index + part.length;
        }
        spacers.push(text.substring(index));
        return {
            spacers: spacers,
            matchings: matchings
        };
    };

    // Splits text nodes into marked and not marked groups.
    //
    const splitNodes = (nodes, keywords) => {
        for (let i = 0; i < keywords.length; ++i) {
            const expression = new RegExp(`${keywords[i]}`, 'gi');
            for (let j = 0; j < nodes.length; ++j) {
                const node = nodes[j];
                const spacers = node.spacers;
                const matchings = node.matchings;
                for (let k = 0; k < spacers.length;) {
                    const parts = findParts(expression, spacers[k]);
                    spacers.splice(k, 1, ...parts.spacers);
                    matchings.splice(k, 0, ...parts.matchings);
                    k += parts.spacers.length;
                }
            }
        }
    };

    // Wraps matched text parts into marked nodes.
    //
    const wrapNodes = (nodes, style) => {
        for (let i = 0; i < nodes.length; ++i) {
            const node = nodes[i];
            const handle = node.handle, parent = handle.parentNode;
            const spacers = node.spacers, matchings = node.matchings;
            for (let j = 0; j < matchings.length; ++j) {
                const spacer = spacers[j];
                const matching = matchings[j];
                if (spacer) {
                    const text = document.createTextNode(spacer);
                    parent.insertBefore(text, handle);
                }
                if (matching) {
                    const wrapper = document.createElement('span');
                    wrapper.className = style;
                    wrapper.innerText = matching;
                    parent.insertBefore(wrapper, handle);
                }
            }
            const spacer = spacers[spacers.length - 1];
            if (spacer) {
                const text = document.createTextNode(spacer);
                parent.insertBefore(text, handle);
            }
          	parent.removeChild(handle);
        }
    };

    const markKeywords = (element, style, keywords) => {
        const nodes = findNodes(element);
        splitNodes(nodes, keywords);
        wrapNodes(nodes, style);
    };
    
    
    // Usage example:
    
    const element = document.querySelector('#text');
    const keywords = ['text', 'underline', 'ital'];
    
    markKeywords(element, 'yellow-marker', keywords);

  </script>
</body>
</html>
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join