Monday, October 19, 2009

Remove all empty XML elements using LINQ

While working on the XamlWriter I had to do some post processing on the generated Xaml. One was to delete all nodes that did not have any attributes or contain any elements. The first pass of removing the empty nodes could generate more empty nodes and hence this problem is recursive in nature.

This was my first attempt
var query = doc.Descendants().Where(c => !c.HasAttributes && c.IsEmpty);
while (query.Count())
   query.Remove();

This could be certainly improved using the .Any operator

var query = doc.Descendants().Where(c => !c.HasAttributes && c.IsEmpty);
while (query.Any())
   query.Remove();

The problem with the above solution(s) is that the query is executed twice, once for the Any/Count method and once for the Remove method. The next approach provided 2x performance gains (in the best case scenario) over the above approach

void RemoveEmptyNodes(XElement root)
{
   var query = root.Descendants().Where(e => !e.HasAttributes && e.IsEmpty).ToList();
   query.Remove();
   if (query.Any())
      RemoveEmptyNodes(root);
}

The reason for the performance improvement is that although both the Any and the Remove operations are executed on the query, the .ToList operator executes and caches the result and hence the query is executed only once.

The concern I have with the above recursive solution is the memory requirement for the local variable query which is of type IEnumerable.

Maybe something like this will help but I don't understand GC (garbage collection) in .NET well enough to recommend this approach

void RemoveEmptyNodes(XElement root)
{
   var query = root.Descendants().Where(e => !e.HasAttributes && e.IsEmpty).ToList();
   query.Remove();
   if (query.Any())
   {
      query = null;
      RemoveEmptyNodes(root);
   }
}

Anyone with better or alternate approaches please post your thoughts as comments.

Friday, October 16, 2009

Silverlight 3 XamlWriter - v0.4 - DataTemplate support

The new version of XamlWriter with DataTemplate support is now available. Check the previous posts for download and usage information.

While it is possible to recreate the DataTemplate it is not possible to recreate ContentTemplate. Hence I couldn't add support for ContentTemplate. There is also support for the Style class but there is an issue with it.

The DataTemplate example

Thursday, October 15, 2009

Silverlight 3 XamlWriter - v0.3 - Storyboard and TransformGroup support

An update every day - Wow! Thought I'd keep the momentum going while I have a liiittle free time. Once it goes on the back-burner, it will lie there to bite the dust!

If anyone has tried the XamlWriter (which I am sure no one has, yet!) it would be clear that it doesn't work for Storyboards and TransformGroups. That is because they are a slightly special case. Why? I'll leave that for some future post. Anyways it's fixed now.

Please check out my previous post for download and usage information.

I also thought I'd take this opportunity to point out some issue that I encountered. If you had read my previous posts on this subject you would know that I used reflection to implement the XamlWriter.

The way to retrieve the properties of an object using reflection is like this -
  PropertyInfo[] props = target.GetType().GetProperties();

I now needed to to check if the property is readonly. The reason for this is that a readonly property cannot be used in Xaml. So I went ahead and used the CanWrite property to check if the property could be written to, like this -
  if (!prop.CanWrite)

But, guess what? It doesn't work in all cases. For example, the IsFocussed property of a Button control returns true for CanWrite. I put the IsFocussed property in the Xaml and it threw an error indicating clearly that this was a read only property.

So there had to be a different way. After all the Xaml parser knows more about readonly properties than the CanWrite property. That is when I discovered the GetSetMethod method on ProperyInfo. This worked wonderfully -
  if (prop.GetSetMethod == null)

Whether this is a bug or some explainable feature, I don't know. Maybe I will post this on the Silverlight.net forums and see if there is an explanation. In the meantime, there is a workaround for me to continue making progress!

The Storyboard and TransformGroup example

Wednesday, October 14, 2009

Silverlight 3 XamlWriter - v0.2

I was just going through the Silverlight.net forums to see if there was any interesting question to answer and I came across a post which had some interesting Xaml content. This used the Accordian control and looked like a good test case so I copied the Xaml to my project and invoked XamlWriter on it.

And...it did not work! That was expected though. I would be very surprised if it did work.

So, back to the drawing board - a few fixes here, a few fixes there, discovered a few more bugs, fixed them, and stopped when I got it to work with the second Xaml test case.

Get the newer version of XamlWriter from here - XamlWriter.dll

The second example

Tuesday, October 13, 2009

Silverlight 3 XamlWriter - A basic implementation

I am sure that anyone who has worked extensively with Silverlight has felt the need for a XamlWriter at some time. I mostly need it for debugging purposes - to see the xaml of the dynamically assembled controls.

I have been taking the cumbersome route of stepping through the control hierarcy using the debugger, but enough is enough! Just takes too much time. It probably is quicker to just write some code which takes in a FrameworkElement and outputs the Xaml representation of it.

I did find 3 resources on the web that would prove useful (or not!)
  1. XamlWriter by Mehran (rambler.elf on the Silverlight.net forums) - A nice simple reflection based approach that would prove the starting point for my project
  2. XamlWriter by the SilverlightContrib team - Did not try this since it is embedded deep in another project. Not sure if this wuld work for me but since I like to have more control I decided to write one myself (based on Mehran's code)
  3. Silverlight Spy - A third party tool that could be used to extract the Xaml of a specific control. Not so useful for me since I need to hook it in my code
So I decided to take Mehran's code and make it work on Silverlight 3. Then I refined it and added some features and refined it a bit more and added some more features and...

It's by no means complete or well tested but if you feel brave enough
  • just download this file - XamlWriter.dll 
  • add a reference to this file from your project 
  • and use the following snippet of code to test it

using projectsilverlight.blogspot.com.XamlWriter;

XamlWriter xamlWriter = new XamlWriter();
string xaml = xamlWriter.WriteXaml(MyControl, XamlWriterSettings.LogicalTree);

Note that XamlWriter uses System.Xml.Linq for some processing so you will find this dll getting added to the xap as well.

XamlWriterSettings has 3 values which can be combined to get different output. The 4 valid combinations are

WriteXaml(MyControl, XamlWriterSettings.LogicalTree);
WriteXaml(MyControl, XamlWriterSettings.LogicalTree | XamlWriterSettings.AllAttributes);
WriteXaml(MyControl, XamlWriterSettings.VisualTree);
WriteXaml(MyControl, XamlWriterSettings.VisualTree | XamlWriterSettings.AllAttributes);
Like I mentioned before - this is not well tested. In fact, the example below is the only case I have tested. I am putting it up nevertheless to get some feedback. If you find that it is not working for a specific case, send me the Xaml/code that it is not working for and I will take a look. At some point in the future, if still relevant, I will open source this code. But now it is closed source for various reasons - the code quality sucks, haven't taken permission from Mehran yet, want better control so that a single codebase gets improved upon rather than each individual fixing it for his/her needs.

Enjoy!

An example