String Extraction
Ah thought I go back to basics today for a post. An operation I rely on a lot when I’m working with text is the ability to extract strings with delimiters from within another string. RegEx can be quite useful for many scenarios of finding matches and extracting text, but personally for the 10 times a year when I need to use RegEx expressions I tend to relearn most of the cryptic syntax each time. What should take me a couple minutes usually turns into a half an hour of experimenting with RegEx Buddy for me.
So a long time ago I created some helper functions that make it easier to help me with the common task of string extraction. One of them is ExtractString() which is something I use quite frequently to well extract a string from within another string. This can be in small strings or larger strings. The function accepts a source string and a couple of delimiters around which text gets extracted.
Usage looks something like this:
// MS json Date format is "\/Date(9221211)\/"
jsonDate = StringUtils.ExtractString(jsonDate, @"\/Date(", @")\/");
which extracts the only the milliseconds portion of the string inside of the date delimiter. Now here’s actually a good example of why this function is easier than using RegEx – try constructing that particular pattern with RegEx escape codes for some real fun and undecipherable gobbledygook. :-} Been there done that (for the JavaScript portion of the parser and it took a while to get right!).
ExtractString() also has a few additional parameters for case sensitivity and how to behave if the end delimiter is not found which is useful. For example in the following snippet a query string is passed and I need to find the value of value r parameter:
string res = StringUtils.ExtractString(Url,"?r=","&",false,true);
And no, before you ask: I couldn’t use Request.QueryString() because the string in this case is a parsed URL that doesn’t come from a query string input. The function call searches for a r= and & as delimiters, does so case insensitively (false) and specifies that the “&” delimiter can be missing which means the result returns up to the end of the string. In fact to create a generic query string parser you could now write:
public static string GetUrlEncodedKey(string urlEncodedString, string key)
{
string res = StringUtils.ExtractString(urlEncodedString, key + "=", "&", false, true);
return HttpUtility.UrlDecode(res);
}
Again RegEx can solve this fairly easily as well, but doing or’d expressions are a pain to get right and since I use this functionality all the time I don’t want to fuck around with RegEx’s constant relearning curve (for me).
I certainly prefer:
string res = StringUtils.ExtractString(Url,"?r=","&",false,true);
to:
public static string GetUrlEncodedKey(string urlEncodedString, string key)
{
string res = StringUtils.ExtractString(urlEncodedString, key + "=", "&", false, true);
return HttpUtility.UrlDecode(res);
}
I’m not saying that RegEx hasn’t got a place. Obviously for more complex searches RegEx is much more flexible. But for simple repetive scenarios RegEx is overkill and likely more resource intensive than a simple function that finds a couple of string indexes and extracts a substring. And to me at least RegEx usage seriously affects code readibility so if I have a chance to abstract behavior in such a way that I don’t have to use RegEx I will do so vehemently – either by wrapping the RegEx code or creating an alternative.
Anyway, here’s the implementation of ExtractString with various overloads:
/// Extracts a string from between a pair of delimiters. Only the first /// instance is found. ///
/// Input String to work on
/// Beginning delimiter
/// ending delimiter
/// Determines whether the search for delimiters is case sensitive
/// Extracted string or ""
public static string ExtractString(string source, string beginDelim,
string endDelim, bool caseSensitive,
bool allowMissingEndDelimiter)
{
int at1, at2;
if (string.IsNullOrEmpty(source))
return string.Empty;
if (caseSensitive)
{
at1 = source.IndexOf(beginDelim);
if (at1 == -1)
return string.Empty;
at2 = source.IndexOf(endDelim, at1 + beginDelim.Length);
}
else {
//string Lower = source.ToLower(); at1 = source.IndexOf(beginDelim,0,source.Length,StringComparison.OrdinalIgnoreCase);
if (at1 == -1)
return string.Empty;
at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.OrdinalIgnoreCase);
}
if (allowMissingEndDelimiter && at2 == -1)
return source.Substring(at1 + beginDelim.Length);
if (at1 > -1 && at2 > 1)
return source.Substring(at1 + beginDelim.Length, at2 - at1 - beginDelim.Length);
return string.Empty;
}
public static string ExtractString(string source, string beginDelim, string endDelim, bool caseSensitive)
{
return ExtractString(source, beginDelim, endDelim, caseSensitive, false);
}
public static string ExtractString(string source, string beginDelim, string endDelim)
{
return ExtractString(source, beginDelim, endDelim, false, false);
}
It’s pretty basic stuff, but it’s one of those handy utility functions that are useful so frequently it’s nice to have them in my Utility set of classes (StringUtils to be exact).
So I’m curious to hear what the RegEx aficionados will have to say. I wonder if RegEx could be used inside of the function – the big issue I always have with RegEx in reusable/generic scenarios is that patterns that contain the start and end delimiters have to escaped properly. If I have backslash in the starting delimiter a generic expression will die the painful RegEx parsing death. So (he asks mockingly) - could the generic internals of ExtractString be re-written to use RegEx instead of indexOf and SubString to retrieve the string match – frankly I don’t know how (and it’s not really necessary, but an interesting thought).
BTW, for some of the FoxPro folks lurking this function might look familiar. It closely matches West Wind Web Connection’s Extract method as well as VFP’s native StrExtract functions on which I had relied for many years. I think the above functions was one of the first things I ever created in .NET because its use was sorely missed when starting out in .NET.
Hopefully this method will be useful to some of you.
Other Posts you might also like
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Resolving Paths To Server Relative Paths in .NET Code
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Back to Basics: Rewriting a URL in ASP.NET Core
- Getting the Client IP Address in ASP.NET Core
The Voices of Reason
# re: String Extraction
# re: String Extraction
I've used all sorts of languages on different platforms over the years, but trying to build my own patterns always takes ages.
Happy to use regex for checking formats of email address etc. when I know I can use tried and tested patterns.
# re: String Extraction
public static string ExtractString(this string source, string beginDelimiter, string endDelimiter)
# re: String Extraction
# re: String Extraction
http://www.microsoft.com/events/series/detail/webcastdetails.aspx?seriesid=15&webcastid=5103
As always, thanks for the code! I'm a recovering FoxPro Lurker and we've been "leveraging" your generous public posts for years now. You 'da man, Rick!
Happy Christmas.
# re: String Extraction
Another Useful class, SubstringInfo
http://blog.sb2.fr/post/2008/12/01/SubstringInfo-The-Simple-Way-To-SubString.aspx
# re: String Extraction
# re: String Extraction
Very useful method, I read your response to Antonio regarding the use of extension methods :) Anyway you might want to check out my extension method's library with few methods that I regularly use http://zubairdotnet.blogspot.com/2008/11/c-extension-methods-library.html and let me know what do you think of them.
# re: String Extraction
# re: String Extraction
To all -- One quote about Regex I remember from way back -- "When you try to solve a problem using Regex, you actually have two problems." :)
# re: String Extraction
as a regex guy i'd never use this. not trying to knock your class, but to me this seems much less efficient with very little more "ease of use" than regex if one spends like five minutes with it.
for example, here's your string:
// json Date format is "\/Date(9221211)\/"
here's my regex:
@"\\/Date\((\d+)\)\\."
You could even have named capture like this:
"\\/Date\((?<yournumber>\d+)\)\\."
Then you can refer to it as match.Groups["yournumber"].Value
anyhoo, thats my thoughts.
# re: String Extraction
Don't get me wrong - RegEx is great when used appropriately especially for more complex parsing where it's difficult to create parsing code. That's where RegEx shines. But for simpler tasks - a little bit of plain CLR code is not much more voluminous and much more readable and likely to perform better than RegEx.
# re: String Extraction
I don't think I'd put it in a loop.
No doubt it could be used for some string chopping, but I never feel inclined to stop writing code long enough to learn, debug, test the regex needed and end up just doing it the brute force way.
# re: String Extraction
# re: String Extraction
I think skilled C# developers should master regex and use it as a core tool in their work. A great book for this is "Mastering Regular Expressions" from O'Reilly press. Its a great book for deeply understanding such a gem of a tool for software developers.
Probably making Rick's point more than I want to, here is the last regex I wrote just this week:
</?(?:art:|ecom:)(StandAloneArticleListDisplay|StandAloneProductListDisplay)(?:(?:\s+(\w+)(?:\s*=\s*(?:""(.*?)""|'(.*?)'|[^'"">\s]+))?)+\s*|\s*)/?>
using that with:
MatchCollection mc = s_regex.Matches(pageHtml);
allows me to find specific server side controls and all of their attributes that are very easy to iterate through with the MatchCollection.
# re: String Extraction
As I mentioned before the biggest problem for me with RegEx is that I just don't use them enough to remember the 'syntax' if you can call it that. Occasionally I do get into areas where I use them heavily but I just don't have the wiring to remember arcane switches long term - everytime I pick it back up I have to relearn the switches and dig out my Visibone cheat sheet. I do remember the concepts (grouping, match sets, forward matching etc.) so it's not as bad as it used to be but it's still often more work that writing a small and testable piece of code.
There are definitely other situations where regEx is so much easier and more efficient than hand coding and in those cases it would really be silly to not use them. Right tool for the job I suppose.
Oh and the O'Reilly RegEx book you mention is great - I've been through it and use it for reference frequently. That and the Visibone RegEx guide and RegEx buddy are my crutches. :-}
# re: String Extraction
# re: String Extraction
I was looking for the equivalent of the VFP STREXTRACT() function and stumbled across this blog so thank you very much for saving me some time!
A great enhancement would be to add a parameter to look for the
# re: String Extraction