Skip to content

Instantly share code, notes, and snippets.

@huoxudong125
Forked from josheinstein/DynamicRegex.cs
Created November 28, 2016 01:11
Show Gist options
  • Save huoxudong125/2cf211f84bbf4434ed9fc7b9b9a2fd42 to your computer and use it in GitHub Desktop.
Save huoxudong125/2cf211f84bbf4434ed9fc7b9b9a2fd42 to your computer and use it in GitHub Desktop.
public sealed class DynamicMatch : DynamicObject
{
private readonly Regex _Regex;
private readonly Match _Match;
public DynamicMatch( Regex regex, Match match )
{
Contract.Requires( regex != null, "Regex cannot be null." );
Contract.Requires( match != null, "Match cannot be null." );
_Regex = regex;
_Match = match;
}
public override IEnumerable<string> GetDynamicMemberNames( )
{
// i honestly don't know where the hell this is used
return _Regex.GetGroupNames( );
}
public override bool TryConvert( ConvertBinder binder, out object result )
{
// supports casting back to the original Match object
if ( binder.Type == typeof( Match ) ) {
result = _Match;
return true;
}
// supports casting the match to its string representation
// is the complete match result
if ( binder.Type == typeof( String ) ) {
if ( _Match.Success ) {
result = _Match.Value;
return true;
}
else {
result = null;
return true;
}
}
// supports casting the match to a boolean indicating success
if ( binder.Type == typeof( Boolean ) || binder.Type == typeof(Boolean?) ) {
result = _Match.Success;
return true;
}
return base.TryConvert( binder, out result );
}
public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out object result )
{
// supports 'match[x]' where x is a named capture group
// or 'match[n]' where n is an implicit capture group index
if ( indexes.Length == 1 ) {
Group group = _Match.Groups[Convert.ToString( indexes[0] )];
if ( group != null && group.Success ) {
result = group.Value;
return true;
}
else {
result = null;
return true;
}
}
return base.TryGetIndex( binder, indexes, out result );
}
public override bool TryGetMember( GetMemberBinder binder, out object result )
{
// supports 'match.x' where x is a named capture group
Group group = _Match.Groups[binder.Name];
if ( group != null && group.Success ) {
result = group.Value;
return true;
}
else {
result = null;
return true;
}
}
public override bool TryUnaryOperation( UnaryOperationBinder binder, out object result )
{
// supports 'if (match) {...}'
if ( binder.Operation == ExpressionType.IsTrue ) {
result = _Match.Success;
return true;
}
// supports 'if (!match) {...}'
if ( binder.Operation == ExpressionType.IsFalse || binder.Operation == ExpressionType.Not ) {
result = !_Match.Success;
return true;
}
return base.TryUnaryOperation( binder, out result );
}
public static bool operator ==( DynamicMatch x, bool y )
{
// supports 'if (match == true) {...}'
return x._Match.Success == y;
}
public static bool operator !=( DynamicMatch x, bool y )
{
// supports 'if (match != true) {...}'
return x._Match.Success != y;
}
public static bool operator ==( DynamicMatch x, string y )
{
// supports 'if (match == "foo") {...}'
return x._Match.Value == y;
}
public static bool operator !=( DynamicMatch x, string y )
{
// supports 'if (match != "foo") {...}'
return x._Match.Value != y;
}
}
public static class DynamicRegex
{
public static dynamic Match( string input, string pattern )
{
var regex = new Regex( pattern );
var match = regex.Match( input );
return new DynamicMatch( regex, match );
}
}
[TestClass]
public class DynamicRegexTests
{
[TestMethod]
public void DynamicMatchConvertsToMatch( )
{
var dynamicMatch = DynamicRegex.Match( "Hello World", @"H[A-Za-z]+" );
Match regularMatch = dynamicMatch;
Assert.IsTrue( regularMatch.Success );
Assert.AreEqual( "Hello", regularMatch.Value );
}
[TestMethod]
public void SuccessfulMatchConvertsToTrue( )
{
var match = DynamicRegex.Match( "Hello World", @"H[A-Za-z]+" );
// Convert To Boolean
Assert.IsTrue( (bool)match, "Successful match should convert to true." );
// Compare To Boolean
if ( !match ) { Assert.Fail( "Successful match should convert to true." ); }
if ( match == false ) { Assert.Fail( "Successful match should convert to true." ); }
if ( match != true ) { Assert.Fail( "Successful match should convert to true." ); }
}
[TestMethod]
public void UnsuccessfulMatchConvertsToFalse( )
{
var match = DynamicRegex.Match( "Hello World", @"^H[A-Za-z]+$" );
// Convert To Boolean
Assert.IsFalse( (bool)match, "Unsuccessful match should convert to false." );
// Compare To Boolean
if ( match ) { Assert.Fail( "Unsuccessful match should convert to false." ); }
if ( match == true ) { Assert.Fail( "Unsuccessful match should convert to false." ); }
if ( match != false ) { Assert.Fail( "Unsuccessful match should convert to false." ); }
}
[TestMethod]
public void SuccessfulMatchConvertsToString( )
{
var match = DynamicRegex.Match( "Hello World", @"H[A-Za-z]+" );
// Convert To String
Assert.AreEqual( "Hello", (string)match, "Successful match should convert to Match.Value." );
// Compare To String
if ( match != "Hello" ) { Assert.Fail( "Successful match should convert to Match.Value." ); }
}
[TestMethod]
public void UnsuccessfulMatchConvertsToNull( )
{
var match = DynamicRegex.Match( "Hello World", @"^H[A-Za-z]+$" );
// Convert To String
Assert.IsNull( (string)match, "Unsuccessful match should convert to null." );
// The following will always fail - C# does the null check without converting
// if (match != null) { Assert.Fail("..."); }
}
[TestMethod]
public void SuccessfulCapturesAccessedAsProperties( )
{
var match = DynamicRegex.Match( "123-456-7890", @"(?<npa>d{3})-?(?<nxx>d{3})-?(d{4})" );
Assert.AreEqual( "123", match.npa, "Capture group npa could not be accessed by name." );
Assert.AreEqual( "456", match.nxx, "Capture group nxx could not be accessed by name." );
}
[TestMethod]
public void SuccessfulCapturesAccessedAsIndexer( )
{
var match = DynamicRegex.Match( "123-456-7890", @"(?<npa>d{3})-?(?<nxx>d{3})-?(d{4})" );
Assert.AreEqual( "123", match["npa"], "Capture group npa could not be accessed by name." );
Assert.AreEqual( "456", match["nxx"], "Capture group nxx could not be accessed by name." );
Assert.AreEqual( "7890", match[1], "Unnamed capture group could not be accessed by number." );
Assert.AreEqual( "123-456-7890", match[0], "Capture group 0 should return entire match." );
}
[TestMethod]
public void UnsuccessfulMatchReturnsPropertiesAsNull( )
{
// Must match 123-456-7890
// Last 4 digits will cause unsuccessful match
var match = DynamicRegex.Match( "123-456-XXXX", @"(?<npa>d{3})-?(?<nxx>d{3})-?(d{4})" );
Assert.IsNull( match.npa, "Unsuccessful match must return all properties as null." );
Assert.IsNull( match["npa"], "Unsuccessful match must return all properties as null." );
Assert.IsNull( match["1"], "Unsuccessful match must return all properties as null." );
Assert.IsNull( match["0"], "Unsuccessful match must return all properties as null." );
}
[TestMethod]
public void SuccessfulMatchReturnsOptionalGroupAsNull( )
{
// Must match at least 123-456-
// Last 4 digits are optional
var match = DynamicRegex.Match( "123-456-XXXX", @"(?<npa>d{3})-?(?<nxx>d{3})-?(d{4})?" );
Assert.AreEqual( "123", match["npa"], "Successful match should return captured groups as string." );
Assert.AreEqual( "456", match["nxx"], "Successful match should return captured groups as string." );
Assert.AreEqual( "123-456-", match[0], "Successful match should return entire match as string." );
Assert.IsNull( match[1], "Successful match should return missing optional groups as null." );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment