Split string into words and rejoin with additional data
You need to enclose all your alternatives within a non-capturing group, (?:...|...)
. Besides, to further counter eventual issues, I suggest replacing word boundaries with their lookaround unambiguous equivalents, (?<!\w)...(?!\w)
.
Here is a working C# snippet:
var text = "there are big widgets in this phrase blue widgets too";
var words = "big blue widgets";
var pattern = $@"(\s*(?<!\w)(?:{string.Join("|", words.Split(' ').Select(Regex.Escape))})(?!\w)\s*)";
var result = string.Concat(Regex.Split(text, pattern, RegexOptions.IgnoreCase).Select((str, index) =>
index % 2 == 0 && !string.IsNullOrWhiteSpace(str) ? $"<b>{str}</b>" : str));
Console.WriteLine(result);
NOTES
words.Split(' ').Select(Regex.Escape)
- splits thewords
text with spaces and regex-escapes each itemstring.Join("|",...)
re-builds the string inserting|
between the items(?<!\w)
negative lookbehind matches a location that is not immediately preceded with a word char, and(?!\w)
negative lookahead matches a location that is not immediately followed with a word char.
I suggest implementing FSM (Finite State Machine) with 2
states (in and out selection) and Regex.Replace
(we can keep the word as it is - word
or replace it with <b>word
, word<\b>
or <b>word<\b>
)
private static string MyModify(string text, string wordsToExclude) {
HashSet<string> exclude = new HashSet<string>(
wordsToExclude.Split(' '), StringComparer.OrdinalIgnoreCase);
bool inSelection = false;
string result = Regex.Replace(text, @"[\w']+", match => {
var next = match.NextMatch();
if (inSelection) {
if (next.Success && exclude.Contains(next.Value)) {
inSelection = false;
return match.Value + "</b>";
}
else
return match.Value;
}
else {
if (exclude.Contains(match.Value))
return match.Value;
else if (next.Success && exclude.Contains(next.Value))
return "<b>" + match.Value + "</b>";
else {
inSelection = true;
return "<b>" + match.Value;
}
}
});
if (inSelection)
result += "</b>";
return result;
}
Demo:
string wordsToExclude = "big widgets blue if";
string[] tests = new string[] {
"widgets for big blue",
"big widgets are great but better if blue",
"blue",
"great but expensive",
"big and small, blue and green",
};
string report = string.Join(Environment.NewLine, tests
.Select(test => $"{test,-40} -> {MyModify(test, wordsToExclude)}"));
Console.Write(report);
Outcome:
widgets for big blue -> widgets <b>for</b> big blue
big widgets are great but better if blue -> big widgets <b>are great but better</b> if blue
blue -> blue
great but expensive -> <b>great but expensive</b>
big and small, blue and green -> big <b>and small</b>, blue <b>and green</b>