While searching mail in Google, we use the sytax like
from:devcoder hasattachments:true mySearchString on:11-aug
or
mySearchString from:devcoder on:11-aug anotherSearchKeyword
After parsing, I should get the keyvalue pair such as (from, devcoder), (on, 11-aug).
What is the best way to implement this parsing in c#.
To Linq-ify Jason's answer:
string s = "from:devcoder hasattachments:true mySearchString on:11-aug";
var keyValuePairs = s.Split(' ')
.Select(x => x.Split(':'))
.Where(x => x.Length == 2)
.ToDictionary(x => x.First(), x => x.Last());
Split by space, then for each component of the split, split it by :
. Then proceed accordingly. Roughly:
string s = "from:devcoder hasattachments:true mySearchString on:11-aug";
var components = s.Split(' ');
var blocks = components.Select(component => component.Split(':'));
foreach(var block in blocks) {
if(block.Length == 1) {
Console.WriteLine("Found {0}", block[0]);
}
else {
Console.WriteLine(
"Found key-value pair key = {0}, value = {1}",
block[0],
block[1]
);
}
}
Output:
Found key-value pair key = from, value = devcoder
Found key-value pair key = hasattachments, value = true
Found mySearchString
Found key-value pair key = on, value = 11-aug
Output from your second string:
Found mySearchString
Found key-value pair key = from, value = devcoder
Found key-value pair key = on, value = 11-aug
Found anotherSearchKeyword
Here is one regular expression-based approach I have used in the past; it supports prefixes in combination with quoted strings.
A more correct/robust/performant approach would involve writing a simple parser, however in most usage scenarios the time and effort associated with implementing and testing the parser would be vastly disproportionate to the gains.
private static readonly Regex searchTermRegex = new Regex(
@"^(
\s*
(?<term>
((?<prefix>[a-zA-Z][a-zA-Z0-9-_]*):)?
(?<termString>
(?<quotedTerm>
(?<quote>['""])
((\\\k<quote>)|((?!\k<quote>).))*
\k<quote>?
)
|(?<simpleTerm>[^\s]+)
)
)
\s*
)*$",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture
);
private static void FindTerms(string s) {
Console.WriteLine("[" + s + "]");
Match match = searchTermRegex.Match(s);
foreach(Capture term in match.Groups["term"].Captures) {
Console.WriteLine("term: " + term.Value);
Capture prefix = null;
foreach(Capture prefixMatch in match.Groups["prefix"].Captures)
if(prefixMatch.Index >= term.Index && prefixMatch.Index <= term.Index + term.Length) {
prefix = prefixMatch;
break;
}
if(null != prefix)
Console.WriteLine("prefix: " + prefix.Value);
Capture termString = null;
foreach(Capture termStringMatch in match.Groups["termString"].Captures)
if(termStringMatch.Index >= term.Index && termStringMatch.Index <= term.Index + term.Length) {
termString = termStringMatch;
break;
}
Console.WriteLine("termString: " + termString.Value);
}
Console.WriteLine();
}
public static void Main (string[] args)
{
FindTerms(@"two terms");
FindTerms(@"prefix:value");
FindTerms(@"some:""quoted term""");
FindTerms(@"firstname:Jack ""the Ripper""");
FindTerms(@"'quoted term\'s escaped quotes'");
FindTerms(@"""unterminated quoted string");
}
Output:
[two terms]
term: two
termString: two
term: terms
termString: terms
[prefix:value]
term: prefix:value
prefix: prefix
termString: value
[some:"quoted term"]
term: some:"quoted term"
prefix: some
termString: "quoted term"
[firstname:Jack "the Ripper"]
term: firstname:Jack
prefix: firstname
termString: Jack
term: "the Ripper"
termString: "the Ripper"
['quoted term\'s escaped quotes']
term: 'quoted term\'s escaped quotes'
termString: 'quoted term\'s escaped quotes'
["unterminated quoted string]
term: "unterminated quoted string
termString: "unterminated quoted string
First Split()
on space, then you have an array containing all search terms. Then you loop over them to find the ones that Contains()
a colon (:) and Split()
those again on the colon.