What you want is negative lookbehinds:
(?<!</?[^>]*|&[^;]*)(\b160\b|\bspa\b)
and replace with
<span class="highlight">$1</span>
The negative lookbehind syntax is:
(?<! ... )
, which indicates that the keyword cannot be preceded by a certain pattern. That pattern in this case is either the beginning of a tag
</?[^>]*
or the beginning of an HTML entity
&[^;]*
that isn't complete.
</?[^>]*
indicates an open bracket, possibly followed by a slash, followed by any number of chars that aren't close brackets.
&[^;]*
indicates an ampersand followed by any number of chars that aren't semicolons.
Here's how to incorporate this into your C# code:
string[] keywords = { "spa", "160", "whatever" };
Regex.Replace(htmlContent, "(?<!</?[^>]*|&[^;]*)(\b" + string.Join("\b|\b", keywords) + "\b)", "<span class=\"highlight\">$1</span>", RegexOptions.IgnoreCase);
EDIT: I incorporated the good point made by Andreas Gieriet - that you need to ensure you are matching complete "words" only by matching word boundaries with
\b
.