The ASP.NET system libraries offer powerful string manipulation functionality in the form of extensive Regular Expression support as well as String-specific functions such as Split and Replace. In 99% of cases these functions are sufficient to use to quickly manipulate strings. However in one case I had to put together a special function of my own.
What was the problem & why build a custom function?
The UI to be developed was rather simple. I wanted to write a basic search Web page that contained a TextBox for the user to enter their search text, a Submit button to run the search, and a ListView control to display the search results.
Although from the UI perspective everything was very straightforward, the catch became clear with the requirement to have the search term bolded in the search results shown by the ListView control. This became a problem for the following reasons:
- The routine needed to inject start and end HTML formatting tags into the search results surrounding the term being searched for. In this case I wanted to inject <b> and </b> tags.
- The highlighting routine needed to inject the HTML tags any number of times into one result depending on the number of matches. This means that for the search result “Test search result” and a search term of the letter “e” the routine would have to inject <b> and </b> tags three times around each “e”.
- The routine needed to preserve the capitalization of the original text. This was a bit of a catch for the logic of the routine, invalidating the use of a direct replace function. So, for example, if the user had their caps lock on and typed a search as “TEST” and the search results phrase was ”Test search result”, I would want the search to match on the word “test” and place the surrounding HTML tags around the capitalization found in the database so that the result would appear as “<b>Test</b> search result”.
I ended up creating a simple generic function in VB.NET that could be called from multiple columns in the search results and would return the correctly formatted results string.
The function would take parameters such as the search term, the search results string from the database, the start and end HTML tags to use to surround the search matches with.
I’m including the generic function below. In the case of the ListView control that I was using, I called the function in the ListView’s ItemDataBound event for the columns/rows I wanted to control the output on.
Here is the code for the generic function to inject the HTML into the search results:
Public Function injectHTMLToString(ByVal strHTMLToInjectStartTag As String, ByVal strHTMLToInjectEndTag As String, _ ByVal strFullStringToInject As String, ByVal strStringPartToUse As String) As String Dim strTestOut As String = String.Empty Dim testValOne As String = strFullStringToInject.ToLower() Dim testComparator As String = strStringPartToUse.ToLower() Dim intReplaceLength As Int32 = strHTMLToInjectStartTag.Length + strHTMLToInjectEndTag.Length + testComparator.Length While (testValOne).IndexOf(testComparator) >= 0 Dim intIndexLoc As Int32 = (testValOne).IndexOf(strStringPartToUse.ToLower()) strFullStringToInject = strFullStringToInject.Insert((intIndexLoc + testComparator.Length), strHTMLToInjectEndTag) strFullStringToInject = strFullStringToInject.Insert(intIndexLoc, strHTMLToInjectStartTag) Dim strSnippetStart As String = testValOne.Substring(0, testValOne.IndexOf(testComparator)) Dim strSnippetEnd As String = testValOne.Substring((testValOne.IndexOf(testComparator) + testComparator.Length), _ (testValOne.Length - (testValOne.IndexOf(testComparator) + testComparator.Length))) testValOne = strSnippetStart & Space(intReplaceLength) & strSnippetEnd End While Return strFullStringToInject End Function
Here is the code to call the generic formatting function from a test ListView control’s ItemDataBound event:
Protected Sub ListView1_ItemDataBound(sender As Object, e As ListViewItemEventArgs) Handles ListView1.ItemDataBound Dim strSearchTerm As String = (txtSearch3.Text).Trim If strSearchTerm.Length > 0 Then Dim lvRow As ListViewDataItem If e.Item.ItemType = ListViewItemType.DataItem Then lvRow = TryCast(e.Item, ListViewDataItem) If Not lvRow Is Nothing Then Dim rowLabelOne As Label = DirectCast(e.Item.FindControl("lblFieldOne"), Label) Dim rowLabelTwo As Label = DirectCast(e.Item.FindControl("lblFieldTwo"), Label) Dim strFoundValueOne As String = "" & Trim(DataBinder.Eval(lvRow.DataItem, "FldOne").ToString()) Dim strFoundValueTwo As String = "" & Trim(DataBinder.Eval(lvRow.DataItem, "FldTwo").ToString()) rowLabelOne.Text = injectHTMLToString("<b>", "</b>", strFoundValueOne, strSearchTerm) rowLabelTwo.Text = injectHTMLToString("<b>", "</b>", strFoundValueTwo, strSearchTerm) End If End If End If End Sub
Summary of the generic formatting function
The code for the generic formatting function may seem a bit unclear at first, so for readability I’m breaking out the logic into plain English below:
- For starters the function clones the search term and the string to search into lower case variables.
- The function then gets the numeric dimension of the start and end HTML tags when associated with the search term into the variable intReplaceLength.
- The function then uses a while loop to iterate through each match of search term contained within the string to search.
- At the start of the while loop, the first occurring index of the search term is derived from the cloned lower case string to search.
- The HTML starting and ending tags are then injected into the originally passed-in string to search using the ASP.NET Insert function.
- Then the cloned lower case string to search is broken into two parts and reassembled with blank spaces replacing the string to search. This is done to make sure that the while loop will not be infinite and that the length of the originally passed-in string to search matches exactly with the length of the cloned lower case string to search.