Evjen c10.tex V2 - 01/28/2008 2:13pm Page 537 Chapter 10: Working with XML and LINQ to XML Figure 10-6 There is no end to the number of places you can find and use XML: files, databases, Web sites, and services. Sometimes you will want to manipulate the XML via queries or programmatically, and some- times you will want to take the XML ‘‘tree’’ and transform it into a tree of a different form. XSLT XSLT is a tree transformation language also written in XML syntax. It’s a strange hybrid of a declarative and a programmatic language, and some programmers would argue that it’s not a language at all. Others, who use a number of XSLT scripting extensions, would argue that it is a very powerful language. Regardless of the controversy, XSLT transformations are very useful for changing the structure of XML files quickly and easily, often using a very declarative syntax. 537 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 538 Chapter 10: Working with XML and LINQ to XML The best way to familiarize yourself with XSLT is to look at an example. Remember that the Books.xml file used in this chapter is a list of books and their authors. The XSLT in Listing 10-18 takes that document and transforms it into a document that is a list of authors. Listing 10-18: Books.xslt XSLT <?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:element name="Authors"> <xsl:apply-templates select="//book"/> </xsl:element> </xsl:template> <xsl:template match="book"> <xsl:element name="Author"> <xsl:value-of select="author/first-name"/> <xsl:text> </xsl:text> <xsl:value-of select="author/last-name"/> </xsl:element> </xsl:template> </xsl:stylesheet> Remember that XSLT is XML vocabulary in its own right, so it makes sense that it has its own namespace and namespace prefix. XSLT is typically structured with a series of templates that match elements in the source document. The XSLT document doesn’t describe what the result looks like as much as it declares what steps must occur for the transformation to succeed. Remembering that your goal is an XML file with a list of authors, you match on the root node of Books.xml and output a root element for the resulting document named < Authors >.Then< xsl:apply-templates select = "//book"/ > indicates to the processor that it should continue looking for templates that, in this case, match the XPath expression //book . Below the first template is a second template that handles all book matches. It outputs a new element named < Author >. XSLT is very focused on context, so it is often helpful to imagine a cursor that is on a particular element of the source document. Immediately after outputting the < Author > element, the processor is in the middle of the template match on the book element. All XPath expressions in this example are relative to the book element. So the < xsl:value-of select = "author/first-name" > directive searches for the author’s first name relative to the book element. The < xsl:text >< /xsl:text > directive is interesting to note because it is explicit and a reminder that a difference exists between significant white space and insignificant white space. It is important, for example, that a space is put between the author’s first and last names, so it must be called out explicitly. The resulting document is shown in Figure 10-7. This example only scratches the surface of XSLT’s power. Although a full exploration of XSLT is beyond the scope of this book, other books by Wrox Press cover the topic more fully. Remember that the .NET Framework implements the 1.0 implementation of XSLT. As of this writing, XSLT 2.0 and XPath 2.0 are W3C Recommendations, and Microsoft is working on CTPs (Community Technology Previews) of XSLT 2 functionality. More details are available on the Microsoft XmlTeam blog at http://blogs.msdn.com/ xmlteam/archive/2007/01/29/xslt-2-0.aspx . 538 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 539 Chapter 10: Working with XML and LINQ to XML Figure 10-7 Figure 10-7 shows the resulting XML as the Books.xslt transformation is applied to Books.xml .You can apply XSLT transformations in a number of ways, both declarative and programmatic. These are described in the following sections. XslCompiledTransform The XslTransform class was used in the .NET Framework 1.x for XSLT transformation. In the .NET Framework 2.0, the XslCompiledTransform class is the new XSLT processor. It is such an improvement that XslTransform is deprecated and marked with the Obsolete attribute. The compiler will now advise you to use XslCompiledTransform . The system generates MSIL code on the call to Compile() and the XSLT executes many times faster than previous techniques. This compilation technique also includes full debugging support from within Visual Studio, which is covered a little later in this chapter. The XPathDocument is absolutely optimized for XSLT transformations and should be used instead of the XmlDocument if you would like a 15 to 30 percent performance gain in your transformations. Remember that XSLT contains XPath, and when you use XPath, use an XPathDocument . According to the team’s numbers, XSLT is 400 percent faster in .NET Framework 2.0. XslCompiledTransform has only two methods: Load and Transform . The compilation happens without any effort on your part. Listing 10-19 loads the Books.xml fileintoan XPathDocument and transforms it using Books.xslt and an XslCompiledTransform . Even though there are only two methods, there are 14 overrides for Transform and 6 for Load . That may seem a little daunting at first, but there is a simple explanation. The Load method can handle loading a stylesheet from a string, an XmlReader , or any class that implements IXPathNavigable .An XsltSettings object can be passed in optionally with any of the 539 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 540 Chapter 10: Working with XML and LINQ to XML previous three overloads, giving you six to choose from. XsltSettings includes options to enable the document() XSLT–specific function via the XsltSettings.EnableDocumentFunction property or enable embedded script blocks within XSLT via XsltSettings.EnableScript . These advanced options are disabled by default for security reasons. Alternatively, you can retrieve a pre-populated XslSettings object via the static property XsltSettings.TrustedXslt , which has enabled both these settings. If you think it is odd that the class that does the work is called the XslCompiledTransform and not the XsltCompiledTransform , but XsltSettings includes the t, remember that the t in XSLT means transformation. Note in Listing 10-19 that the Response.Output property eliminates an unnecessary string allocation. In the example, Response.Output is a TextWriter wrapped in an XmlTextWriter and passed directly to the Execute method. Listing 10-19: Executing an XsltCompiledTransform VB Response.ContentType = "text/xml" Dim xsltFile As String = Server.MapPath("books.xslt") Dim xmlFile As String = Server.MapPath("books.xml") Dim xslt As New XslCompiledTransform() xslt.Load(xsltFile) Dim doc As New XPathDocument(xmlFile) xslt.Transform(doc, New XmlTextWriter(Response.Output)) C# Response.ContentType = "text/xml"; string xsltFile = Server.MapPath("books.xslt"); string xmlFile = Server.MapPath("books.xml"); XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load(xsltFile); XPathDocument doc = new XPathDocument(xmlFile); xslt.Transform(doc, new XmlTextWriter(Response.Output)); Named arguments may be passed into an XslTransform or XslCompiledTransform if the stylesheet takes parameters. The following code snippet illustrates the use of XslArgumentList : XslTransform transformer = new XslTransform(); transformer.Load("foo.xslt"); XslArgumentList args = new XslArgumentList(); args.Add("ID", "SOMEVALUE"); transformer.Transform("foo.xml", args, Response.OutputStream); 540 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 541 Chapter 10: Working with XML and LINQ to XML The XML resulting from an XSLT transformation can be manipulated with any of the system.XML APIs that have been discussed in this chapter. One common use of XSLT is to flatten hierarchical and, some- times, relational XML documents into a format that is more conducive to output as HTML. The results of these transformations to HTML can be placed inline within an existing ASPX document. The new XSLTC.exe Command-Line Compiler Compiled stylesheets are very useful but there is a slight performance hit as the stylesheets are compiled at runtime, so the .NET Framework 3.5 has introduced XSLTC.exe , a command-line XSLT compiler. Usage is simple — you simply pass in as many source XSLT files as you like and specify the assembly output file. From a Visual Studio 2008 command prompt, use the following: Xsltc /c:Wrox.Book.CompiledStylesheet books.xslt /out:Books.dll Now, add a reference to the newly created Books.dll in your project from Listing 10-19, and change one line: XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load(typeof(Wrox.Book.MyCompiledStyleSheet)); Rather than loading the XSLT from a file, it’s now loaded pre-compiled directly from the generated assembly. Using the XSLT Compiler makes deployment much easier as you can put many XSLTs in a single assembly, but most important, you eliminate code-generation time. If your XSLT uses msxsl:script elements, that code will be compiled into separate assemblies, one per language used. You can merge these resulting assemblies with ILMerge, located at http://research .microsoft.com/ ∼ mbarnett/ILMerge.aspx , as a post-build step.XML Web Server Control. XSLT transformations can also be a very quick way to get information out to the browser as HTML. Consider this technique as yet another tool in your toolbox. HTML is a tree, and HTML is a cousin of XML, so an XML tree can be transformed into an HTML tree. A benefit of using XSLT transformations to create large amounts of static text, like HTML tables, is that the XSLT file can be kept external to the application. You can make quick changes to its formatting without a recompile. A problem when using XSLT transformations is that they can become large and very unruly when someone attempts to use them to generate the entire user interface experience. The practice was in vogue in the mid-nineties to use XSLT transformations to generate entire Web sites, but the usefulness of this technique breaks down when complex user interactions are introduced. That said, XSLT has a place, not only for transforming data from one format to another, but also for creating reasonable chunks of your user interface-as long as you don’t go overboard. In the next example, the output of the XSLT is HTML rather than XML. Note the use of the < xsl:output method = "html" > directive. When this directive is omitted, the default output of an XSLT transformation is XML. This template begins with a match on the root node. It is creating an HTML fragment rather than an entire HTML document. Its first output is the < h3 > tag with some static text. Next comes a table tag and the header row, and then the < xsl:apply-template > element selects all books within the source XML document. For every book element in the source document, the second template is invoked with the responsibility of outputting one table row per book. Calls to < xsl:value-of > select each of the book’s subnodes and output them within the < td > tags (see Listing 10-20). 541 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 542 Chapter 10: Working with XML and LINQ to XML Listing 10-20: BookstoHTML.xslt used with the XML Web Server Control XSLT <?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:b="http://example.books.com" version="1.0"> <xsl:output method="html"/> <xsl:template match="/"> <h3>List of Authors</h3> <table border="1"> <tr> <th>First</th><th>Last</th> </tr> <xsl:apply-templates select="//b:book"/> </table> </xsl:template> <xsl:template match="b:book"> <tr> <td><xsl:value-of select="b:author/b:first-name"/></td> <td><xsl:value-of select="b:author/b:last-name"/></td> </tr> </xsl:template> </xsl:stylesheet> ASPX <%@ Page Language="VB" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"><title>HTML/XSLT Transformation</title></head> <body> <form id="form1" runat="server"> <div> <asp:Xml ID="Xml1" Runat="server" DocumentSource="~/Books.xml" TransformSource="~/bookstoHTML.xslt"/> </div> </form> </body> </html> Notice the use of namespace prefixes in Listing 10-20. The source namespace is declared with the prefix b as in xmlns:b="http://example.books.com" and the b prefix is subsequently used in XPath expressions like //b:book . The XSLT in Listing 10-20 can use the XSLTCommand to perform this transformation on the server-side because the entire operation is declarative and requires just two inputs — the XML document and the XSLT document. The XML Web server control makes the transformation easy to perform from the ASPX page and does not require any language-specific features. The DocumentSource property of the control holds the path to the Books.xml file, whereas the TransformSource property holds the path to the BookstoHTML.xslt file: < h3 > List of Authors < /h3 > < table border="1" > < tr > < th > First < /th > < th > Last < /th > < /tr > 542 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 543 Chapter 10: Working with XML and LINQ to XML < tr > < td > Benjamin < /td > < td > Franklin < /td > < /tr > < tr > < td > Herman < /td > < td > Melville < /td > < /tr > < tr > < td > Sidas < /td > < td > Plato < /td > < /tr > < /table > The results of this transformation are output inline to this HTML document and appear between the two < div > tags. You see the results of this HTML fragment in the previous code and in the browser’s output shown in Figure 10-8. Figure 10-8 XSLT Debugging One of the exciting new additions to ASP.NET 2.0 and Visual Studio was XSLT debugging. Visual Studio 2005 enabled breakpoints on XSLT documents and Visual Studio 2008 adds XSLT Debugger Data Breakpoints — the ability to break on nodes within the source XML document. However, be aware that XSLT debugging is available only in the Professional and Team System versions of Visual Studio and only when using the XslCompiledTransform class. By passing the Boolean value true into the constructor of XslCompiledTransform , you can step into and debug your XSLT transformations within the Microsoft Development Environment. Dim xslt As New XslCompiledTransform(True) 543 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 544 Chapter 10: Working with XML and LINQ to XML In Listing 10-19, change the call to the constructor of XslCompiledTransform to include the value true and set a breakpoint on the Transform method. When you reach that breakpoint, press F11 to step into the transformation. Figure 10-9 shows a debugging session of the Books.xslt/Books.xml transformation in process. Figure 10-9 In the past, debugging XSLT was largely an opaque process that required a third-party application to troubleshoot. The addition of debugging XSLT to Visual Studio means that your XML experience is just that much more integrated and seamless. Databases and XML You have seen that XML can come from any source whether it be a Web service, a file on disk, an XML fragment returned from a Web server, or a database. SQL server and ADO have rich support for XML, starting with the ExecuteXmlReader method of the System.Data.SqlCommand class. Additional support for XML on SQL Server 2000 is included with SQLXML 3.0 and its XML extensions, and SQL Server 2005 has native XML data type support built right in. 544 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 545 Chapter 10: Working with XML and LINQ to XML FOR XML AUTO You can modify a SQL query to return XML with the FOR XML AUTO clause. If you take a simple query such as select * from customers , you just change the statement like so: select * from customers FOR XML AUTO XML AUTO returns XML fragments rather than a full XML document with a document element. Each row in the database becomes one element; each column in the database becomes one attribute on the element. Notice that each element in the following result set is named Customers because the select clause is from customers : < Customers CustomerID="ALFKI" CompanyName="Alfreds Futterkiste" ContactName="Maria Anders" ContactTitle="Sales Representative" Address="Obere Str. 57" City="Berlin" PostalCode="12209" Country="Germany" Phone="030-0074321" Fax="030-0076545" / > < Customers CustomerID="ANATR" CompanyName="Ana Trujillo Emparedados y helados" ContactName="Ana Trujillo" ContactTitle="Owner" Address="Avda. de la Constitucion 2222" City="Mexico D.F." PostalCode="05021" Country="Mexico" Phone="(5) 555-4729" Fax="(5) 555-3745" / > If you add ELEMENTS to the query like so select * from customers FOR XML AUTO, ELEMENTS you get an XML fragment like this: < Customers > < CustomerID > ALFKI < /CustomerID > < CompanyName > Alfreds Futterkiste < /CompanyName > < ContactName > Maria Anders < /ContactName > < ContactTitle > Sales Representative < /ContactTitle > < Address > Obere Str. 57 < /Address > < City > Berlin < /City > < PostalCode > 12209 < /PostalCode > < Country > Germany < /Country > < Phone > 030-0074321 < /Phone > < Fax > 030-0076545 < /Fax > < /Customers > < Customers > < CustomerID > ANATR < /CustomerID > < CompanyName > Ana Trujillo Emparedados y helados < /CompanyName > < ContactName > Ana Trujillo < /ContactName > < ContactTitle > Owner < /ContactTitle > < Address > Avda. de la Constitucion 2222 < /Address > < City > Mexico D.F. < /City > < PostalCode > 05021 < /PostalCode > < Country > Mexico < /Country > < Phone > (5) 555-4729 < /Phone > < Fax > (5) 555-3745 < /Fax > < /Customers > 545 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 546 Chapter 10: Working with XML and LINQ to XML The previous example is just a fragment with no document element. To perform an XSLT transformation, you need a document element (sometimes incorrectly referred to as the ‘‘root node’’), and you probably want to change the < Customers > elements to < Customer >. By using an alias like as Customer in the select statement, you can affect the name of each row’s element. The query select * from Customers as Customer for XML AUTO, ELEMENTS changes the name of the element to < Customer >. Now, put together all the things you’ve learned from this chapter and create an XmlDocument ,editand manipulate it, retrieve data from SQL Server as an XmlReader , and style that information with XSLT into an HTML table all in just a few lines of code. First, add a document element to the document retrieved by the SQL query select * from Customers as Customer for XML AUTO, ELEMENTS , as shown in Listing 10-21. Listing 10-21: Retrieving XML from SQL Server 2000 using FOR XML AUTO VB Dim connStr As String = "database=Northwind;Data Source=localhost;" & _ " User id=sa;pwd=wrox" Dim x As New XmlDocument() Dim xpathnav As XPathNavigator = x.CreateNavigator() Using conn As New SqlConnection(connStr) conn.Open() Dim command As New SqlCommand("select * from Customers as Customer " & _ "for XML AUTO, ELEMENTS", conn) Using xw As XmlWriter = xpathnav.PrependChild() xw.WriteStartElement("Customers") Using xr As XmlReader = command.ExecuteXmlReader() xw.WriteNode(xr, True) End Using xw.WriteEndElement() End Using End Using Response.ContentType = "text/xml" x.Save(Response.Output) C# string connStr = "database=Northwind;Data Source=localhost;User id=sa;pwd=wrox"; XmlDocument x = new XmlDocument(); XPathNavigator xpathnav = x.CreateNavigator(); using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); SqlCommand command = new SqlCommand( "select * from Customers as Customer for XML AUTO, ELEMENTS", conn); using (XmlWriter xw = xpathnav.PrependChild()) { xw.WriteStartElement("Customers"); using (XmlReader xr = command.ExecuteXmlReader()) { xw.WriteNode(xr, true); } 546 . D.F. < /City > < PostalCode > 050 21 < /PostalCode > < Country > Mexico < /Country > < Phone > (5) 55 5-4729 < /Phone > < Fax > (5) 55 5 -37 45 < /Fax > < /Customers > 54 5 Evjen. 10-8. Figure 10-8 XSLT Debugging One of the exciting new additions to ASP. NET 2.0 and Visual Studio was XSLT debugging. Visual Studio 20 05 enabled breakpoints on XSLT documents and Visual Studio 2008 adds. 2222" City="Mexico D.F." PostalCode=" 050 21" Country="Mexico" Phone=" (5) 55 5-4729" Fax=" (5) 55 5 -37 45& quot; / > If you add ELEMENTS to the query like