XML isn't new, it's been around for quite awhile, and will most likely be around for awhile longer. The basic concept of nodes and attributes has been used in a lot of different areas of design in an effort to bring uniformity. Admittedly in the past, I wasn't to thrilled about dealing with some of the nastier XML documents, but with the arrival of LINQ to XML, me and XML became friends again.
Looking at an XAML file, we can see that essentially it is just namespaced XML. It can be loaded an parsed like any other XML document. Here I have an example of a exported canvas from Expression Design.
<?xml version="1.0" encoding="utf-8"?>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="LinqToXaml" Width="3600" Height="3600" Clip="F1 M 0,0L 3600,0L 3600,3600L 0,3600L 0,0">
<Canvas x:Name="Layer_1" Width="3600" Height="3600" Canvas.Left="0" Canvas.Top="0">
<Path x:Name="Path" Width="1361" Height="1371" Canvas.Left="989.5" Canvas.Top="899.5" Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FF451616" Data="F1 M 1670,900C 1795.22,900 1912.53,934.093 2013.28,993.567C 2214.72,1112.47 2350,1332.82 2350,1585C 2350,1828.51 2223.87,2042.34 2033.82,2163.81C 1928.62,2231.06 1803.82,2270 1670,2270C 1294.45,2270 990,1963.32 990,1585C 990,1490.42 1009.03,1400.32 1043.44,1318.37C 1146.67,1072.51 1388.33,900 1670,900 Z "/>
<Path x:Name="Path_0" Width="968.593" Height="1018.48" Canvas.Left="2567.52" Canvas.Top="408.874" Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FF9F0E0E" Data="F1 M 3050,410C 2570,600 2560,990 2570,1010C 2580,1030 2840,1130 2940,1300C 3040,1470 3300,1420 3300,1420L 3400,1050C 3400,1050 3660,950 3460,730C 3260,510 3050,400 3050,410 Z "/>
</Canvas>
</Canvas>
So in the above file we have a Canvas object as a container, and it contains a Canvas with some basic size attributes. That canvas then contains two path objects that I drew while in Expression Design. So lets drop a lot of the fluff and just get down to how we actually get to the values of this XAML file. Use whatever platform your comfortable with, console application, web site, or a LINQPad type program. First thing to do is make sure you have access the the xml objects were going to use.
using System.Xml.Linq;
Now we need to load our xaml file. The XDocument object handles this for us nicely in one handy line of code.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
So, the XDocument.Load method brings in for us our entire document, and if you take the xDoc object and output it as a string, you'd have your entire Xaml file at this point. But, accessing individual values in the XAML file requires accessing it via the namespaces attached to it. If you look at the root canvas, you'll see the namespaces listed as attributes.
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="LinqToXaml" Width="3600" Height="3600" Clip="F1 M 0,0L 3600,0L 3600,3600L 0,3600L 0,0">
The first name space starts with xmlns and this is the document namespace, the second namespace is has the "x" prefix to identify it, and anything that lives in this namespace will use that "x" prefix to identify that it belongs to that namespace.
Lets add get our XNamespace object declared and initialize it's value.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
XNamespace xspc = xDoc.Root.Name.Namespace;
XNamespace otherSpc = "http://schemas.microsoft.com/winfx/2006/xaml";
This is a handy way to make the root name space available to us in code as Is shown above by accessing the namespace property of the root, and for example you can also see how to hard-code the namespace if needed. With these namespaces setup, were now ready to write our LINQ statements to access our XAML.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
XNamespace xspc = xDoc.Root.Name.Namespace;
XNamespace otherSpc = "http://schemas.microsoft.com/winfx/2006/xaml";
var theVals = from x in xDoc.Descendants(xspc + "Canvas")
select x.Elements();
This query isn't very good, because it'll give us duplicate data, but the main point to see is that when specifying the descendants we want, we specify the root namespace and join it to the Canvas object we wanted to look for. This is the basic concept of how we access namespaces with LINQ to XML.
Lets start to refine our query a bit and return only our Path Objects.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
XNamespace xspc = xDoc.Root.Name.Namespace;
XNamespace otherSpc = "http://schemas.microsoft.com/winfx/2006/xaml";
var theVals = from x in xDoc.Descendants(xspc + "Canvas")
select x.Elements(xspc + "Path");
Now were filtering our query to say only give me the path elements, again when accessing the path element, we include our root name space declaration. But what if we want to access the Name attribute in this xaml file which happens to be in a different namespace. We extend our query and include the other namespace.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
XNamespace xspc = xDoc.Root.Name.Namespace;
XNamespace otherSpc = "http://schemas.microsoft.com/winfx/2006/xaml";
var theVals = from x in xDoc.Descendants(xspc + "Canvas")
select x.Elements(xspc + "Path").Attributes(otherSpc + "Name");
The other name space is used because that attribute is prefixed by the "x" namespace. Do we have to use the root namespace though when we access the other attributes that are not in the "x" namespace? No.
XDocument xDoc = XDocument.Load("C:\\LinqToXAML.xaml");
XNamespace xspc = xDoc.Root.Name.Namespace;
XNamespace otherSpc = "http://schemas.microsoft.com/winfx/2006/xaml";
var theVals = from x in xDoc.Descendants(xspc + "Canvas")
select x.Elements(xspc + "Path").Attributes("Data");
As you can see, when we access the Data attribute, of our Path object, we didn't need to specify the namespace like we did when accessing the object itself. Obviously I haven't provided the output results of these queries, but I encourage you to use whatever your tool of choice is and try this out for yourself.
In our last query, we accessed the Data property of a path object. If we wanted to find a particular path object, we could of put in a where clause to our query and specified the name of the path object, and then taken it's "Data" values and manipulated it as we see fit.