[updated to try out a Live Writer plugin for code formatting ... let's see if that makes the code samples more readable!]
In a previous post we presented an overview of the XML Features in the "Orcas" Community Technology Preview. This post gives some more details and examples for what we call the "bridge classes" that let one use other System.Xml APIs over a LINQ to XML tree. For example, LINQ to XML users can now create a tree in memory with an XmlWriter application or as the output of an XSLT transformation, validate a loaded tree against an XSD schema, use XPath 1.0 to query and XSLT 1.0 to transform the tree, and so forth.
Why do this? Can't a developer already do anything with a LINQ query that can be done with XPath? Doesn't the combination of LINQ queries and functional construction do more or less what XSLT does? In principle, sure. In practice, there are some good reasons to build a bridge to the rest of System.Xml:
The examples below refer to a sample XML instance that describes the make, model, color, and 4-wheel drive capability of automobiles.
string autoData = @" <Autos> <Auto> <Make>Ford</Make> <Model>Ranger</Model> <Color>white</Color> <FourXFour>false</FourXFour> </Auto> <Auto> <Make>Toyota</Make> <Model>4-Runner</Model> <Color>gold</Color> <FourXFour>true</FourXFour> </Auto> <Auto> <Make>Chevy</Make> <Model>Cavalier</Model> <Color>white</Color> <FourXFour>false</FourXFour> </Auto> </Autos>";
These bridge classes exist outside of the core System.Xml.Linq namespace; extension methods supporting this additional functionality are brought into scope by referencing the appropriate namespace.
XPath is enabled by referencing the System.Xml.XPath namespace:
using System.Xml.XPath;
This brings into scope CreateNavigator overloads to create XpathNavigator objects, XPathEvaluate overloads to evaluate an XPath expression, and XPathSelectElement[s] overloads that work much like SelectSingleNode and XPatheSelectNodes methods in the System.Xml DOM API.
For example, to display cars that have four wheel drive:
XDocument doc1 = XDocument.Parse(autoData); foreach (var model in doc1 .XPathSelectElements("//Auto[FourXFour='true']")) Console.WriteLine(model);
To use namespace-qualified XPath expressions, it is necessary to pass in a NamespaceResolver object, just as with DOM.
XSLT is enabled by referencing the System.Xml.Xsl namespace
using System.Xml.Xsl;
Let's consider an example where we want to transform the automobile data (which has been loaded into an XDocument object named doc1) with the following stylesheet:
string xslMarkup = @" <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> <xsl:template match='//Autos'> <html> <body> <h1>Autos in Stock</h1> <table> <tr> <th>Make</th> <th>Model</th> <th>Color</th> </tr> < xsl:apply-templates></xsl:apply-templates> </table> </body> </html> </xsl:template> <xsl:template match='Auto'> <tr> <td><xsl:value-of select='Make'/></td> <td><xsl:value-of select='Model'/></td> <td><xsl:value-of select='Color'/></td> </tr> </xsl:template> </xsl:stylesheet>";
You can perform the transformation by a) creating an XDocument object to hold the result; b) loading the stylesheet into an XslCompiledTransform object; c) creating a "bridge class" XmlReader over the input XDocument object and using that as input to the transformation.
XDocument newTree = new XDocument(); using (XmlWriter writer = newTree.CreateWriter()) { XslCompiledTransform xslTransformer = new XslCompiledTransform(); xslTransformer.Load(XmlReader.Create(new StringReader(xslMarkup))); xslTransformer.Transform(doc1.CreateReader(), writer); } Console.WriteLine(newTree);
A few things to note:
You can validate an XElement tree against an XML schema via extensions method in the System.Xml.Schema namespace. This is exactly the same functionality that was shipped in .NET 2.0, with only a "bridge" to expose the classes in that namespace to LINQ to XML.
To bring this functionality into scope, use:
using System.Xml.Schema;
You can now use the .NET 2.0 classes and methods to populate XmlSchemaObject / XmlSchemaSet objects. There will be methods available to Validate() XElement, XAttribute, or XDocument objects against the schema and optionally populate a post schema validation infoset as annotations on the LINQ to XML tree.
For example, here is a schema for the sample Autos data (generated by the XML Editor in Visual Studio):
string xsdMarkup = @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'> <xs:element name='Autos'> <xs:complexType> <xs:sequence> <xs:element maxOccurs='unbounded' name='Auto'> <xs:complexType> <xs:sequence> <xs:element name='Make' type='xs:string' /> <xs:element name='Model' type='xs:string' /> <xs:element name='Color' type='xs:string' /> <xs:element name='FourXFour' type='xs:boolean' /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>";
XmlSchemaSet schemas = new XmlSchemaSet(); schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup))); bool errors = false; doc1.Validate(schemas, (sender, e) => { Console.WriteLine(e.Message); errors = true; }, true); Console.WriteLine("doc1 {0}", errors ? "did not validate" : "validated"); DumpInvalidNodes(doc1.Root);
The code for the DumpInvalidNodes() utility method is:
static void DumpInvalidNodes(XElement el) { if (el.GetSchemaInfo().Validity != XmlSchemaValidity.Valid) Console.WriteLine("Invalid Element {0}", el.AncestorsAndSelf() .Aggregate("", (s, i) => s + i.Name.ToString() + "/")); foreach (XAttribute att in el.Attributes()) if (att.GetSchemaInfo().Validity != XmlSchemaValidity.Valid) Console.WriteLine("Invalid Attribute {0}", att.Parent.AncestorsAndSelf() .Aggregate("", (s, i) => s + i.Name.ToString() + "/") + att.Name.ToString() ); foreach (XElement child in el.Elements()) DumpInvalidNodes(child); }
It is important to remember that the bridge classes are intended to be just that - a bridge from LINQ to XML to the rest of System.Xml, not a seamless integration. There are some impedance mismatches that result from conscious design decisions to eliminate some of the aspects of System.Xml that annoy people. These mismatches include:
In the short term, bridging these impedance mismatches consumes processor cycles, so it is not as fast to do a schema validation or XSLT transformation over an XDocument object than it is over XmlDocument or XPathDocument. We're working on that, but we don't recommend using the bridge classes for performance-critical operations.
We would very much like to hear from you about how you put these classes to work, what additional features you might need, to hear about performance issues, etc.