Highlight text in a RichTextBox Control - c#

I am trying to highlight multiple lines of specific text in a RichTextBox.
Here is my code for highlighting text:
public void HighlightMistakes(RichTextBox richTextBox)
{
string[] phrases = { "Drivers Do Not Match", "Current Does Not Match", "No Drivers Found" };
foreach (var phrase in phrases)
{
int startIndex = 0;
while (startIndex <= richTextBox.TextLength)
{
int phraseStartIndex = richTextBox.Find(phrase, startIndex, RichTextBoxFinds.None);
if (phraseStartIndex != -1)
{
richTextBox.SelectionStart = phraseStartIndex;
richTextBox.SelectionLength = phrase.Length;
richTextBox.SelectionBackColor = Color.Yellow;
}
else break;
startIndex += phraseStartIndex + phrase.Length;
}
}
}
Here is how I add text to RTB and call function above:
foreach (var a in resultList)
{
richTextBox1.AppendText("\n"+a + "\n");
HighlightMistakes(richTextBox1);
}
However, HighlightMistakes doesn't work the way I would like it to. The idea is to highlight all string values specified in phrases array and that doesn't happen every time.
Examples:
I am not sure why some of the lines are skipped and some of them are not.

If you have nothing against a simple Regex method, you can use Regex.Matches to match your list of phrases against the text of your RichTextBox.
Each Match in the collection of Matches contains both the Index (the position inside the text) where the match is found and its Length, so you can simply call .Select(Index, Length) to select a phrase and highlight it.
The pattern used is the string resulting from joining the phrases to match with a Pipe (|).
Each phrase is passed to Regex.Escape(), since the text may contain metacharacters.
If you want to considere the case, remove RegexOptions.IgnoreCase.
using System.Text.RegularExpressions;
string[] phrases = { "Drivers Do Not Match",
"Current Does Not Match",
"No Drivers Found" };
HighlightMistakes(richTextBox1, phrases);
private void HighlightMistakes(RichTextBox rtb, string[] phrases)
{
ClearMistakes(rtb);
string pattern = string.Join("|", phrases.Select(phr => Regex.Escape(phr)));
var matches = Regex.Matches(rtb.Text, pattern, RegexOptions.IgnoreCase);
foreach (Match m in matches) {
rtb.Select(m.Index, m.Length);
rtb.SelectionBackColor = Color.Yellow;
}
}
private void ClearMistakes(RichTextBox rtb)
{
int selStart = rtb.SelectionStart;
rtb.SelectAll();
rtb.SelectionBackColor = rtb.BackColor;
rtb.SelectionStart = selStart;
rtb.SelectionLength = 0;
}

Related

Search button, not case sensitive accepting special characters

I have a search button "find next" that searches in a RichTextBox, the only problem is, when I search for "[e]" then it will mark any "e" in the RichTextBox. And if I search for "[", then the program will crash. Here is my code:
private void downBtn_Click(object sender, EventArgs e)
{
string SearchWord = textBox1.Text;
if (SearchWord.Length > 0)
{
if (SearchWord != prevWord)
{
index = 0;
prevWord = SearchWord;
}
Regex reg = new Regex(SearchWord, RegexOptions.IgnoreCase);
foreach (Match find in reg.Matches(richTextBox1.Text))
{
if (find.Index >= index)
{
richTextBox1.Select(find.Index, find.Length);
richTextBox1.Focus();
index = find.Index + find.Length;
break;
}
}
}
}
Try escaping your search term so that it does not include characters used by regular expressions.
Use the Regex.Escape method to do so.
So you could change your code to:
string escapedSearchTerm = Regex.Escape(SearchWord)
Regex reg = new Regex(escapedSearchTerm, RegexOptions.IgnoreCase);

Highlighting the multiple keywords

Iam trying to highlight the multiple keywords in gridview.I tried with forloop but it highlight only the first item from the array.
protected string HighlightText(string searchWord, string inputText)
{
// string[] strArray = new string[] { "Hello", "Welcome" };
string s = "d,s";
// Split string on spaces.
// ... This will separate all the words.
string[] words = s.Split(',');
for (int i = 0; i < words.Length; i++)
{
//Console.WriteLine(word);
searchWord = words[i];
Regex expression = new Regex(searchWord.Replace(" ", "|"), RegexOptions.IgnoreCase);
return expression.Replace(inputText, new MatchEvaluator(ReplaceKeywords));
}
return string.Empty;
}
Advance thanks.
This was the out put Iam getting only the keyword "d" get highlighted I need to highlight keyword "s" also...
Can you try something like this, instead of looping for keywords 1 by 1
string inputText = "this is keyword1 for test and keyword4 also";
Regex keywords = new Regex("keyword1|keyword2|keyword3|keyword4");
//keywords = keywords.Replace("|", "\b|\b"); //or use \b between keywords
foreach (Match match in keywords.Matches(inputText))
{
//get match.Index & match.Length for selection and color it
}

Is there a better way to implement Shift+Tab or Decrease Indent?

this is how i implemented Shift-Tab or decrease indent... the result on screenr
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && e.Key == Key.Tab)
{
// Shift+Tab
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
string selText = txtEditor.SelectedText;
string text = txtEditor.Text;
// find new lines that are followed by 1 or more spaces
Regex regex = new Regex(Environment.NewLine + #"(\s+)");
Match m = regex.Match(selText);
string spaces;
while (m.Success)
{
GroupCollection grps = m.Groups;
spaces = grps[1].Value;
int i = 0;
// remove 1 space on each loop to a max of 4 spaces
while (i < 4 && spaces.Length > 0)
{
spaces = spaces.Remove(0, 1);
i++;
}
// update spaces in selText
selText = selText.Remove(grps[1].Index, grps[1].Length).Insert(grps[1].Index, spaces);
m = regex.Match(selText, grps[1].Index + spaces.Length);
}
// commit changes to selText to text
text = text.Remove(selStart, selLength).Insert(selStart, selText);
// decrease indent of 1st line
// - find 1st character of selection
regex = new Regex(#"\w");
m = regex.Match(text, selStart);
int start = selStart;
if (m.Success) {
start = m.Index;
}
// - start search for spaces
regex = new Regex(Environment.NewLine + #"(\s+)", RegexOptions.RightToLeft);
m = regex.Match(text, start);
if (m.Success) {
spaces = m.Groups[1].Value;
int i = 0;
while (i < 4 && spaces.Length > 0) {
spaces = spaces.Remove(0, 1); // remove 1 space
i++;
}
text = text.Remove(m.Groups[1].Index, m.Groups[1].Length).Insert(m.Groups[1].Index, spaces);
selStart = m.Groups[1].Index;
}
txtEditor.Text = text;
txtEditor.SelectionStart = selStart;
txtEditor.SelectionLength = selText.Length;
e.Handled = true;
}
the code looks messy and i wonder if theres a better way.
Personally, I wouldn't use Regex for this.
Untested, probably needs modification:
public static class StringExtensions
{
// Removes leading white-spaces in a string up to a maximum
// of 'level' characters
public static string ReduceIndent(this string line, int level)
{
// Produces an IEnumerable<char> with the characters
// of the string verbatim, other than leading white-spaces
var unindentedChars = line.SkipWhile((c, index) => char.IsWhiteSpace(c) && index < level);
return new string(unindentedChars.ToArray());
}
// Applies a transformation to each line of a string and returns the
// transformed string
public static string LineTransform(this string text, Func<string,string> transform)
{
//Splits the string into an array of lines
var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
//Applies the transformation to each line
var transformedLines = lines.Select(transform);
//Joins the transformed lines into a new string
return string.Join(Environment.NewLine, transformedLines.ToArray());
}
}
...
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && e.Key == Key.Tab)
{
// Reduces the indent level of the selected text by applying the
// 'ReduceIndent' transformation to each line of the text.
string replacement = txtEditor.SelectedText
.LineTransform(line => line.ReduceIndent(4));
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
txtEditor.Text = txtEditor.Text
.Remove(selStart, selLength)
.Insert(selStart, replacement);
txtEditor.SelectionStart = selStart;
txtEditor.SelectionLength = replacement.Length;
e.Handled = true;
}
EDIT:
Added comments to the code as per the request of the OP.
For more info:
Extension Methods
Func<T, TResult> delegate
Enumerable.SkipWhile extension method
Lambda Expressions
I'm thinking freely as I have never implemented a text editor.
What if you represent each line by an object with an indentation property, which is reflected in the rendering of the line. Then it would be easy to increase and decrease the indent.

Find substring ignoring specified characters

Do any of you know of an easy/clean way to find a substring within a string while ignoring some specified characters to find it. I think an example would explain things better:
string: "Hello, -this- is a string"
substring to find: "Hello this"
chars to ignore: "," and "-"
found the substring, result: "Hello, -this"
Using Regex is not a requirement for me, but I added the tag because it feels related.
Update:
To make the requirement clearer: I need the resulting substring with the ignored chars, not just an indication that the given substring exists.
Update 2:
Some of you are reading too much into the example, sorry, i'll give another scenario that should work:
string: "?A&3/3/C)412&"
substring to find: "A41"
chars to ignore: "&", "/", "3", "C", ")"
found the substring, result: "A&3/3/C)41"
And as a bonus (not required per se), it will be great if it's also not safe to assume that the substring to find will not have the ignored chars on it, e.g.: given the last example we should be able to do:
substring to find: "A3C412&"
chars to ignore: "&", "/", "3", "C", ")"
found the substring, result: "A&3/3/C)412&"
Sorry if I wasn't clear before, or still I'm not :).
Update 3:
Thanks to everyone who helped!, this is the implementation I'm working with for now:
http://www.pastebin.com/pYHbb43Z
An here are some tests:
http://www.pastebin.com/qh01GSx2
I'm using some custom extension methods I'm not including but I believe they should be self-explainatory (I will add them if you like)
I've taken a lot of your ideas for the implementation and the tests but I'm giving the answer to #PierrOz because he was one of the firsts, and pointed me in the right direction.
Feel free to keep giving suggestions as alternative solutions or comments on the current state of the impl. if you like.
in your example you would do:
string input = "Hello, -this-, is a string";
string ignore = "[-,]*";
Regex r = new Regex(string.Format("H{0}e{0}l{0}l{0}o{0} {0}t{0}h{0}i{0}s{0}", ignore));
Match m = r.Match(input);
return m.Success ? m.Value : string.Empty;
Dynamically you would build the part [-, ] with all the characters to ignore and you would insert this part between all the characters of your query.
Take care of '-' in the class []: put it at the beginning or at the end
So more generically, it would give something like:
public string Test(string query, string input, char[] ignorelist)
{
string ignorePattern = "[";
for (int i=0; i<ignoreList.Length; i++)
{
if (ignoreList[i] == '-')
{
ignorePattern.Insert(1, "-");
}
else
{
ignorePattern += ignoreList[i];
}
}
ignorePattern += "]*";
for (int i = 0; i < query.Length; i++)
{
pattern += query[0] + ignorepattern;
}
Regex r = new Regex(pattern);
Match m = r.Match(input);
return m.IsSuccess ? m.Value : string.Empty;
}
Here's a non-regex string extension option:
public static class StringExtensions
{
public static bool SubstringSearch(this string s, string value, char[] ignoreChars, out string result)
{
if (String.IsNullOrEmpty(value))
throw new ArgumentException("Search value cannot be null or empty.", "value");
bool found = false;
int matches = 0;
int startIndex = -1;
int length = 0;
for (int i = 0; i < s.Length && !found; i++)
{
if (startIndex == -1)
{
if (s[i] == value[0])
{
startIndex = i;
++matches;
++length;
}
}
else
{
if (s[i] == value[matches])
{
++matches;
++length;
}
else if (ignoreChars != null && ignoreChars.Contains(s[i]))
{
++length;
}
else
{
startIndex = -1;
matches = 0;
length = 0;
}
}
found = (matches == value.Length);
}
if (found)
{
result = s.Substring(startIndex, length);
}
else
{
result = null;
}
return found;
}
}
EDIT: here's an updated solution addressing the points in your recent update. The idea is the same except if you have one substring it will need to insert the ignore pattern between each character. If the substring contains spaces it will split on the spaces and insert the ignore pattern between those words. If you don't have a need for the latter functionality (which was more in line with your original question) then you can remove the Split and if checking that provides that pattern.
Note that this approach is not going to be the most efficient.
string input = #"foo ?A&3/3/C)412& bar A341C2";
string substring = "A41";
string[] ignoredChars = { "&", "/", "3", "C", ")" };
// builds up the ignored pattern and ensures a dash char is placed at the end to avoid unintended ranges
string ignoredPattern = String.Concat("[",
String.Join("", ignoredChars.Where(c => c != "-")
.Select(c => Regex.Escape(c)).ToArray()),
(ignoredChars.Contains("-") ? "-" : ""),
"]*?");
string[] substrings = substring.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
string pattern = "";
if (substrings.Length > 1)
{
pattern = String.Join(ignoredPattern, substrings);
}
else
{
pattern = String.Join(ignoredPattern, substring.Select(c => c.ToString()).ToArray());
}
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Index: {0} -- Match: {1}", match.Index, match.Value);
}
Try this solution out:
string input = "Hello, -this- is a string";
string[] searchStrings = { "Hello", "this" };
string pattern = String.Join(#"\W+", searchStrings);
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
}
The \W+ will match any non-alphanumeric character. If you feel like specifying them yourself, you can replace it with a character class of the characters to ignore, such as [ ,.-]+ (always place the dash character at the start or end to avoid unintended range specifications). Also, if you need case to be ignored use RegexOptions.IgnoreCase:
Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
If your substring is in the form of a complete string, such as "Hello this", you can easily get it into an array form for searchString in this way:
string[] searchString = substring.Split(new[] { ' ' },
StringSplitOptions.RemoveEmptyEntries);
This code will do what you want, although I suggest you modify it to fit your needs better:
string resultString = null;
try
{
resultString = Regex.Match(subjectString, "Hello[, -]*this", RegexOptions.IgnoreCase).Value;
}
catch (ArgumentException ex)
{
// Syntax error in the regular expression
}
You could do this with a single Regex but it would be quite tedious as after every character you would need to test for zero or more ignored characters. It is probably easier to strip all the ignored characters with Regex.Replace(subject, "[-,]", ""); then test if the substring is there.
Or the single Regex way
Regex.IsMatch(subject, "H[-,]*e[-,]*l[-,]*l[-,]*o[-,]* [-,]*t[-,]*h[-,]*i[-,]*s[-,]*")
Here's a non-regex way to do it using string parsing.
private string GetSubstring()
{
string searchString = "Hello, -this- is a string";
string searchStringWithoutUnwantedChars = searchString.Replace(",", "").Replace("-", "");
string desiredString = string.Empty;
if(searchStringWithoutUnwantedChars.Contains("Hello this"))
desiredString = searchString.Substring(searchString.IndexOf("Hello"), searchString.IndexOf("this") + 4);
return desiredString;
}
You could do something like this, since most all of these answer require rebuilding the string in some form.
string1 is your string you want to look through
//Create a List(Of string) that contains the ignored characters'
List<string> ignoredCharacters = new List<string>();
//Add all of the characters you wish to ignore in the method you choose
//Use a function here to get a return
public bool subStringExist(List<string> ignoredCharacters, string myString, string toMatch)
{
//Copy Your string to a temp
string tempString = myString;
bool match = false;
//Replace Everything that you don't want
foreach (string item in ignoredCharacters)
{
tempString = tempString.Replace(item, "");
}
//Check if your substring exist
if (tempString.Contains(toMatch))
{
match = true;
}
return match;
}
You could always use a combination of RegEx and string searching
public class RegExpression {
public static void Example(string input, string ignore, string find)
{
string output = string.Format("Input: {1}{0}Ignore: {2}{0}Find: {3}{0}{0}", Environment.NewLine, input, ignore, find);
if (SanitizeText(input, ignore).ToString().Contains(SanitizeText(find, ignore)))
Console.WriteLine(output + "was matched");
else
Console.WriteLine(output + "was NOT matched");
Console.WriteLine();
}
public static string SanitizeText(string input, string ignore)
{
Regex reg = new Regex("[^" + ignore + "]");
StringBuilder newInput = new StringBuilder();
foreach (Match m in reg.Matches(input))
{
newInput.Append(m.Value);
}
return newInput.ToString();
}
}
Usage would be like
RegExpression.Example("Hello, -this- is a string", "-,", "Hello this"); //Should match
RegExpression.Example("Hello, -this- is a string", "-,", "Hello this2"); //Should not match
RegExpression.Example("?A&3/3/C)412&", "&/3C\\)", "A41"); // Should match
RegExpression.Example("?A&3/3/C) 412&", "&/3C\\)", "A41"); // Should not match
RegExpression.Example("?A&3/3/C)412&", "&/3C\\)", "A3C412&"); // Should match
Output
Input: Hello, -this- is a string
Ignore: -,
Find: Hello this
was matched
Input: Hello, -this- is a string
Ignore: -,
Find: Hello this2
was NOT matched
Input: ?A&3/3/C)412&
Ignore: &/3C)
Find: A41
was matched
Input: ?A&3/3/C) 412&
Ignore: &/3C)
Find: A41
was NOT matched
Input: ?A&3/3/C)412&
Ignore: &/3C)
Find: A3C412&
was matched

Search keyword highlight in ASP.Net

I am outputting a list of search results for a given string of keywords, and I want any matching keywords in my search results to be highlighted. Each word should be wrapped in a span or similar. I am looking for an efficient function to do this.
E.g.
Keywords: "lorem ipsum"
Result: "Some text containing lorem and ipsum"
Desired HTML output: "Some text containing <span class="hit">lorem</span> and <span class="hit">ipsum</span>"
My results are case insensitive.
Here's what I've decided on. An extension function that I can call on the relevant strings within my page / section of my page:
public static string HighlightKeywords(this string input, string keywords)
{
if (input == string.Empty || keywords == string.Empty)
{
return input;
}
string[] sKeywords = keywords.Split(' ');
foreach (string sKeyword in sKeywords)
{
try
{
input = Regex.Replace(input, sKeyword, string.Format("<span class=\"hit\">{0}</span>", "$0"), RegexOptions.IgnoreCase);
}
catch
{
//
}
}
return input;
}
Any further suggestions or comments?
try highlighter from Lucene.net
http://incubator.apache.org/lucene.net/docs/2.0/Highlighter.Net/Lucene.Net.Highlight.html
How to use:
http://davidpodhola.blogspot.com/2008/02/how-to-highlight-phrase-on-results-from.html
EDIT:
As long as Lucene.net highlighter is not suitable here new link:
http://mhinze.com/archive/search-term-highlighter-httpmodule/
Use the jquery highlight plugin.
For highlighting it at server side
protected override void Render( HtmlTextWriter writer )
{
StringBuilder html = new StringBuilder();
HtmlTextWriter w = new HtmlTextWriter( new StringWriter( html ) );
base.Render( w );
html.Replace( "lorem", "<span class=\"hit\">lorem</span>" );
writer.Write( html.ToString() );
}
You can use regular expressions for advanced text replacing.
You can also write the above code in an HttpModule so that it can be re used in other applications.
An extension to the answer above. (don't have enough reputation to give comment)
To avoid span from being replaced when search criteria were [span pan an a], the found word was replaced to something else than replace back... not very efficient though...
public string Highlight(string input)
{
if (input == string.Empty || searchQuery == string.Empty)
{
return input;
}
string[] sKeywords = searchQuery.Replace("~",String.Empty).Replace(" "," ").Trim().Split(' ');
int totalCount = sKeywords.Length + 1;
string[] sHighlights = new string[totalCount];
int count = 0;
input = Regex.Replace(input, Regex.Escape(searchQuery.Trim()), string.Format("~{0}~", count), RegexOptions.IgnoreCase);
sHighlights[count] = string.Format("<span class=\"highlight\">{0}</span>", searchQuery);
foreach (string sKeyword in sKeywords.OrderByDescending(s => s.Length))
{
count++;
input = Regex.Replace(input, Regex.Escape(sKeyword), string.Format("~{0}~", count), RegexOptions.IgnoreCase);
sHighlights[count] = string.Format("<span class=\"highlight\">{0}</span>", sKeyword);
}
for (int i = totalCount - 1; i >= 0; i--)
{
input = Regex.Replace(input, "\\~" + i + "\\~", sHighlights[i], RegexOptions.IgnoreCase);
}
return input;
}

Categories