Languages
[Edit]
EN

JavaScript - mark matching text in HTML document with specific marker

5 points
Created by:
Root-ssh
150970

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 using indicated styles (indicated marker).

That logic lets us to group highlighted texts and use different markers.

Marked text in the indicated HTML element using different marker groups - JavaScript
Marked text in the indicated HTML element using different marker groups - JavaScript

Note: to see a simpler version check this article.

Below code usage example:

const element = document.querySelector('#text');
const keywords = [
	{style: 'yellow-marker', patterns: ['text']             },
	{style: 'green-marker',  patterns: ['underline', 'ital']}
];

markKeywords(element, 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;
    }
    
    .green-marker {
        background: #00db00;
    }
    
    .blue-marker {
        background: #5ccbff;
    }
    
  </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) {
			const part = matching[0];
			return {
				index: matching.index,
				length: part.length,
				text: part
			};
        }
		return null;
    };

    // Finds matching and not matching text parts using indicated expression.
    //
    const findParts = (expression, text, style) => {
        expression.lastIndex = 0;
        const spacers = [];
        const matchings = [];
      	let index = 0;
        while(true) {
            const part = findPart(expression, text);
            if (part == null) {
                break;
            }
			const spacer = text.substring(index, part.index);
			const matching = {
				style: style,
				text: part.text,
			};
            spacers.push(spacer);
            matchings.push(matching);
            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 keyword = keywords[i];
          	const style = keyword.style;
          	const patterns = keyword.patterns;
          	for (let m = 0; m < patterns.length; ++m) {
            	const pattern = patterns[m];
                const expression = new RegExp(pattern, '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], style);
                        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) => {
        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 = matching.style;
                    wrapper.innerText = matching.text;
                    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, keywords) => {
        const nodes = findNodes(element);
        splitNodes(nodes, keywords);
        wrapNodes(nodes);
    };
    
    
    // Usage example:
    
    const element = document.querySelector('#text');
    const keywords = [
      	{style: 'yellow-marker', patterns: ['text']             },
      	{style: 'green-marker',  patterns: ['underline', 'ital']},
      	{style: 'blue-marker',  patterns:  ['example']          }
    ];
    
    markKeywords(element, keywords);

  </script>
</body>
</html>

 

See also

  1. JavaScript - mark matching text in HTML document 

Alternative titles

  1. JavaScript - mark matching text in element with specific marker
  2. JavaScript - highlight matching text in HTML document with specific marker
  3. JavaScript - highlight matching text in element with specific marker
  4. JavaScript - find and mark text in the web page with specific marker
Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
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