You could try using several regexps to only search outside angle brackets:
function wpse159789_posts_search( $search, $query ) {
global $wpdb;
if ( ! preg_match( '/' . $wpdb->posts . '\.post_content LIKE \'%(.+)%\'/', $search, $matches, PREG_OFFSET_CAPTURE ) ) {
return $search;
}
$search_str = stripslashes( $matches[1][0] );
// Cater for closed angle pairs embedded in the search string.
for ( $i = 0, $len = mb_strlen( $search_str ); $i < $len; $i++ ) {
$q_searches[] = '(<[^>]*>)?' . preg_quote( mb_substr( $search_str, $i, 1 ) );
}
$q_search = implode( '', $q_searches );
$regexs[] = '^[^<]*' . $q_search; // Before any angle bracket.
$regexs[] = '(<[^>]*>)[^<]*' . $q_search; // After any closed angle bracket pair.
array_unshift( $regexs, implode( ' OR ', array_fill( 0, count( $regexs ), $wpdb->posts . '.post_content RLIKE %s' ) ) );
$search_replace = call_user_func_array( array( $wpdb, 'prepare' ), $regexs );
$search = substr( $search, 0, $matches[0][1] ) . $search_replace . substr( $search, $matches[0][1] + strlen( $matches[0][0] ) );
return $search;
}
I'm not sure this covers all cases, but seems to work on limited testing ... (it'd be a lot simpler if MySQL regexp supported look behind/ahead).