Something important. Something less important. More important stuff. Just some trivia.more tags"; // Send the pieces array to JS. $pieces = preg_split('/(<[^>]+>)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE); $stripped = "Plain text.Something important. Something less important. More important stuff. Just some trivia.more tags"; $search = 'text.Something important. Something less important. More important stuff. Just'; $found = strpos($stripped, $search); $search_length = strlen($search); $return = ''; $tag = FALSE; $highlight = FALSE; $beginning = FALSE; foreach ($pieces as $piece) { list($text, $index) = $piece; $n = strlen($text); $end_char = $index + $n; if ($tag) { // We make up for the fact that $found was found in the stripped text. $found += $n; } // Found can not be the same as $end_char so it does not matter whether you // use < or <= here. if ($index <= $found && $found < $end_char) { $highlight = TRUE; $beginning = TRUE; } if ($highlight) { // Here <= is a must. $end = $found + $search_length <= $end_char; if ($end) { $highlight = FALSE; } if (!$tag) { $chunk = ''; $highlight_index = $found - $index; if ($beginning) { $chunk .= substr($text, 0, $highlight_index); } // If the piece contains the beginning of the match, then we start in the // piece where the string was found and take as many as the search_length // characters. If the piece does not contain the beginning of the match, // then it needs to be lighlighted from its beginning until the end. The // end can be found by calculating how much of the search is already // matched, which is ($index - $found). $highlight_text = ($beginning || $end) ? substr($text, $beginning ? $highlight_index : 0, $search_length + ($beginning ? 0 : $highlight_index)) : $text; $chunk .= '' . $highlight_text . ""; if ($end) { $chunk .= substr($text, $search_length + $highlight_index); } $text = $chunk; $beginning = FALSE; } } $return .= $text; $tag = !$tag; } print "\n\n$return\n";