Permalink
Browse files

add support for mapping tag attributes to an object; update samples

  • Loading branch information...
1 parent 0c91aac commit 7c8737bf4ceca8158521fbac138157ed129fd393 @handcraftsman handcraftsman committed Jul 16, 2010
View
@@ -8,8 +8,8 @@ A simple utility that lets you use LINQ syntax to query HTML.
<TITLE>The Title</TITLE>
</head>
<body bgcolor='#ffffff'>
- <H1 id='hello' style='greeting'>Hello</H1>
- <H2 id='world' style='greeting'>World</H2>
+ <H1 id='hello' style='greeting' tabkey='1'>Hello</H1>
+ <H2 id='world' style='greeting' tabkey='2'>World</H2>
</body>
</html>";
@@ -63,6 +63,24 @@ A simple utility that lets you use LINQ syntax to query HTML.
.First()
.Content
.ShouldBeEqualTo("World");
+
+mapping
+
+ public class Text
+ {
+ public string Id { get; set; }
+ public int TabKey { get; set; }
+ }
+
+ var text = new Text();
+ parsed
+ .DescendantTags
+ .OfType("H1")
+ .First()
+ .MapTo(text);
+
+ text.Id.ShouldBeEqualTo("hello");
+ text.TabKey.ShouldBeEqualTo(1);
## License
@@ -13,6 +13,7 @@
<FORCE_FOREACH_BRACES_STYLE>ALWAYS_ADD</FORCE_FOREACH_BRACES_STYLE>
<FORCE_IFELSE_BRACES_STYLE>ALWAYS_ADD</FORCE_IFELSE_BRACES_STYLE>
<FORCE_WHILE_BRACES_STYLE>ALWAYS_ADD</FORCE_WHILE_BRACES_STYLE>
+ <INDENT_ANONYMOUS_METHOD_BLOCK>False</INDENT_ANONYMOUS_METHOD_BLOCK>
<INDENT_EMBRACED_INITIALIZER_BLOCK>False</INDENT_EMBRACED_INITIALIZER_BLOCK>
<KEEP_BLANK_LINES_IN_CODE>1</KEEP_BLANK_LINES_IN_CODE>
<KEEP_BLANK_LINES_IN_DECLARATIONS>1</KEEP_BLANK_LINES_IN_DECLARATIONS>
@@ -191,72 +191,6 @@ public void Given_a_list_that_contains_a_tag_with_an_attribute_with_the_requeste
);
}
- [Test]
- public void foo()
- {
- const string html = @"
- <html>
- <head>
- <TITLE>The Title</TITLE>
- </head>
- <body bgcolor='#ffffff'>
- <H1 id='hello' style='greeting'>Hello</H1>
- <H2 id='world' style='greeting'>World</H2>
- </body>
- </html>";
-
- var parsed = HTMLParser.Parse(html);
-
- parsed
- .Head
- .Content
- .ShouldBeEqualTo("The Title");
-
- parsed
- .ChildTags
- .Count()
- .ShouldBeEqualTo(2); // head, body
-
- parsed
- .DescendantTags
- .Count(x => x.Parent.TypeEqualsIgnoreCase("HTML"))
- .ShouldBeEqualTo(2); // head, body
-
- parsed
- .DescendantTags
- .OfType("title")
- .Any()
- .ShouldBeFalse();
-
- parsed
- .DescendantTags
- .OfType("title").IgnoreCase()
- .Any()
- .ShouldBeTrue();
-
- parsed
- .DescendantTags
- .OfType("title").IgnoreCase()
- .First()
- .Parent
- .TypeEqualsIgnoreCase("HEAD")
- .ShouldBeTrue();
-
- parsed
- .DescendantTags
- .WithAttributeNamed("id")
- .HavingValue("world")
- .First()
- .Content
- .ShouldBeEqualTo("World");
-
- parsed
- .DescendantTags
- .WithAttributeNamed("style")
- .Count()
- .ShouldBeEqualTo(2);
- }
-
private void should_not_return_null()
{
_result.ShouldNotBeNull();
@@ -300,5 +300,70 @@ private void with_a_tag_that_has_a_parent()
.First();
}
}
+
+ [TestFixture]
+ public class When_asked_to_map_a_tag_to_an_object
+ {
+ private const int Age = 55;
+ private const decimal Amount = 121.50m;
+ private const double Average = 2.435d;
+ private const bool Enabled = true;
+ private const string Name = "Bob";
+ private DateTime _date = new DateTime(2010, 7, 16);
+ private HTMLDocument _tag;
+ private Person _target;
+
+ [Test]
+ public void Given_a_tag_and_object_for_which_all_properties_can_be_mapped()
+ {
+ Test.Verify(
+ with_a_tag_having_non_default_attribute_values,
+ with_a_target_object_having_matching_property_names_and_types,
+ when_asked_to_map_the_tag_to_the_target,
+ should_map_all_values
+ );
+ }
+
+ public class Person
+ {
+ public int Age { get; set; }
+ public decimal Amount { get; set; }
+ public double Average { get; set; }
+ public DateTime Date { get; set; }
+ public bool Enabled { get; set; }
+ public string Name { get; set; }
+ }
+
+ private void should_map_all_values()
+ {
+ _target.Age.ShouldBeEqualTo(Age, "failed to map age");
+ _target.Amount.ShouldBeEqualTo(Amount, "failed to map amount");
+ _target.Average.ShouldBeEqualTo(Average, "failed to map average");
+ _target.Date.ShouldBeEqualTo(_date, "failed to map date");
+ _target.Enabled.ShouldBeEqualTo(Enabled, "failed to map enabled");
+ _target.Name.ShouldBeEqualTo(Name, "failed to map name");
+ }
+
+ private void when_asked_to_map_the_tag_to_the_target()
+ {
+ _tag.MapTo(_target);
+ }
+
+ private void with_a_tag_having_non_default_attribute_values()
+ {
+ _tag = HTMLParser.Parse(String.Format("<person name='{0}' enabled='{1}' age='{2}' Amount='{3}' average='{4}' date='{5}' />",
+ Name,
+ Enabled.ToString().ToUpper(),
+ Age,
+ Amount,
+ Average,
+ _date.ToString("MM/dd/yyyy")));
+ }
+
+ private void with_a_target_object_having_matching_property_names_and_types()
+ {
+ _target = new Person();
+ }
+ }
}
}
@@ -80,5 +80,27 @@ public string Type
{
get { return _node.Name; }
}
+
+ public void MapTo<T>(T destination)
+ {
+ var properties = destination
+ .GetType()
+ .GetProperties()
+ .Where(x => x.CanWrite)
+ .ToDictionary(x => x.Name.ToLower());
+
+ var attributes = Attributes
+ .ToDictionary(x => x.Name.ToLower(), x => x.Value);
+
+ var matches = attributes.Where(x => properties.ContainsKey(x.Key));
+
+ foreach (var match in matches)
+ {
+ var property = properties[match.Key];
+ PropertySetter
+ .GetFor(property.PropertyType)
+ .SetValue(destination, property, match.Value);
+ }
+ }
}
}
@@ -56,6 +56,7 @@
<Compile Include="IEnumerableHTMLTagExtensions.cs" />
<Compile Include="IEnumerableXmlNodeExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="PropertySetter.cs" />
<Compile Include="XmlNodeListExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace LinqToHtml
+{
+ public class PropertySetter
+ {
+ public static readonly PropertySetter Boolean = new PropertySetter(typeof(bool), (d, p, v) =>
+ {
+ try
+ {
+ bool value = Convert.ToBoolean(v);
+ p.SetValue(d, value, null);
+ }
+ catch
+ {
+ }
+ });
+ public static readonly PropertySetter DateTime = new PropertySetter(typeof(DateTime), (d, p, v) =>
+ {
+ try
+ {
+ var value = Convert.ToDateTime(v);
+ p.SetValue(d, value, null);
+ }
+ catch
+ {
+ }
+ });
+ public static readonly PropertySetter Decimal = new PropertySetter(typeof(decimal), (d, p, v) =>
+ {
+ try
+ {
+ decimal value = Convert.ToDecimal(v);
+ p.SetValue(d, value, null);
+ }
+ catch
+ {
+ }
+ });
+ public static readonly PropertySetter Default = new PropertySetter(typeof(object), (d, p, v) => { });
+ public static readonly PropertySetter Double = new PropertySetter(typeof(double), (d, p, v) =>
+ {
+ try
+ {
+ double value = Convert.ToDouble(v);
+ p.SetValue(d, value, null);
+ }
+ catch
+ {
+ }
+ });
+ public static readonly PropertySetter Int = new PropertySetter(typeof(int), (d, p, v) =>
+ {
+ try
+ {
+ int value = Convert.ToInt32(v);
+ p.SetValue(d, value, null);
+ }
+ catch
+ {
+ }
+ });
+ private static readonly IList<PropertySetter> PropertySetters = new List<PropertySetter>();
+ public static readonly PropertySetter String = new PropertySetter(typeof(string), (d, p, v) =>
+ {
+ try
+ {
+ p.SetValue(d, v, null);
+ }
+ catch
+ {
+ }
+ });
+
+ private readonly Type _type;
+
+ static PropertySetter()
+ {
+ PropertySetters.Add(String);
+ PropertySetters.Add(Decimal);
+ PropertySetters.Add(Double);
+ PropertySetters.Add(Int);
+ PropertySetters.Add(Boolean);
+ PropertySetters.Add(DateTime);
+ }
+
+ private PropertySetter(Type type, Action<object, PropertyInfo, string> setValue)
+ {
+ _type = type;
+ SetValue = setValue;
+ }
+
+ public Action<object, PropertyInfo, string> SetValue { get; private set; }
+
+ public static PropertySetter GetFor(Type type)
+ {
+ return PropertySetters.FirstOrDefault(x => x.IsMatch(type)) ?? Default;
+ }
+
+ public bool IsMatch(Type type)
+ {
+ return type.IsAssignableFrom(_type);
+ }
+ }
+}

0 comments on commit 7c8737b

Please sign in to comment.