VB XML Cookbook, Recipe 1: XML Transformations using XML Literals (Doug Rothaus)

I was working on a blog entry about replacing XSLT transforms with Visual Basic XML Literals. As the entry progressed, I noticed that it was really, really long. So, Avner Aharoni and I talked things over and we decided to break it up into multiple entries and start a series, much like the LINQ Cookbook that was started a few months back.


Introducing the VB XML Cookbook. We’ll use these entries to show quick and easy solutions using XML Literals in Visual Basic. In many cases we’ll reference XSLT and XPath where there’s a direct (or very close) replacement of functionality. You’ll find that consuming and transforming XML is quick and easy using XML Literals, XML Axis Properties, and LINQ to XML—all of which are available starting with Visual Basic 2008.


In this first entry, we’ll show how you can perform simple XML transformations using XML Literals and LINQ. Each sample uses an embedded expression that returns a collection of XML Literals from a LINQ query or another source, such as a property or function that returns a collection of XML Literals. You will see how you can replace an entire XSLT transform with just a few lines of Visual Basic code.


Let’s look at our first example. Attached to this blog entry is an XML file that contains a portion of the Contacts from the AdventureWorks sample database for SQL Server in an XML document (AWContacts.xml). Each <Contact> element has an <EmailAddress> sub-element. The following code creates a new XML document with just the e-mail addresses from the source document.


Imports <xmlns=http://SampleSchema/AWContacts>


 


Public Class XMLCookbook


  Private Sub Recipe1()


    Dim xmlDoc = XDocument.Load(“AWContacts.xml”)


 


    Dim emailDoc = <?xml version=1.0?>


                   <EmailAddresses>


                       <%= xmlDoc.<Contacts>.<Contact>.<EmailAddress> %>


                   </EmailAddresses>


 


    emailDoc.Save(“EmailAddresses.xml”)


  End Sub


End Class


 


This would take considerably more effort using an XSLT transform. Here, you simply use Visual Basic to create a “template” function, and use XML Literals to load and save the XML, as well as create the new XML document. The XML Child Axis property references the collection of <EmailAddress> sub-elements. In place of the <xsl:copy-of> element, you use an embedded expression to add the collection of elements to the new document. In the end, you’ve transformed the document without XSLT or XPath. Let’s take a look at what the XSLT (and Visual Basic Code) would look like for the same sample.


Recipe1.xslt


<?xml version=1.0?>


<xsl:stylesheet


  version=1.0


  xmlns:xsl=http://www.w3.org/1999/XSL/Transform


  xmlns:aw=http://SampleSchema/AWContacts


  xmlns:aci=http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo


  xmlns:crm=http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord


  xmlns:act=http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes>


  <xsl:output method=xml indent=yes/>


  <xsl:template match=aw:Contacts>


    <EmailAddresses>


      <xsl:copy-of select=aw:Contact/aw:EmailAddress/>


    </EmailAddresses>


  </xsl:template>


</xsl:stylesheet>


Recipe1_XSLT Visual Basic Code


 


Public Class XMLCookbook


    Sub Recipe1_XSLT()


        Dim xslTransform As New System.Xml.Xsl.XslCompiledTransform


        xslTransform.Load(“recipe1.xslt”)


 


        Dim reader = Xml.XmlReader.Create(“AWContacts.xml”)


        Dim sw As New System.IO.StreamWriter(“Recipe1.xml”)


        Dim writer = Xml.XmlWriter.Create(sw)


 


        xslTransform.Transform(reader, writer)


        sw.Close()


    End Sub


End Class


 


The XSLT version turns out to be about twice the amount of code and, to be honest, took me much longer to create than the VB version. Notice that the XML namespace support in Visual Basic is simpler than XSLT (1.0) as I can define a default XML namespace for XML elements that do not have an XML namespace identified.


Another important thing to mention is that part of the productivity gain comes from the Intellisense support provided with XML Literals in Visual Basic. By adding the schema files that are also attached to this blog entry and then importing the namespaces for the schemas, I get word completion lists for XML child and descendant elements as well as attributes. For more information, see XML Intellisense in Visual Basic.


Using LINQ


To go further, you can add query functionality to your transform using LINQ. In XSLT, you might use the <xsl:for-each> element to loop through results and the <xsl:if> element to test for a particular condition. Using LINQ, you can accomplish this rather easily.


For example, in our source document, each <Contact> element has an <EmailPromotion> sub-element that identifies whether the contact would like to receive promotion e-mails from the company. The following code creates the same document, but only containing e-mail addresses of contacts that want to receive e-mail promotions.


  Dim promoList = <?xml version=1.0?>


                  <EmailPromotionList>


                      <%= From contact In xmlDoc.<Contacts>.<Contact> _


                          Where contact.<EmailPromotion>.Value > 0 _


                          Select contact.<EmailAddress> %>


                  </EmailPromotionList>


Comparable XSLT code would look like the following.


  <xsl:template match=aw:Contacts>


    <EmailAddresses>


      <xsl:for-each select=aw:Contact>


        <xsl:if test=aw:EmailPromotion &gt; 0>


          <xsl:copy-of select=aw:EmailAddress/>


        </xsl:if>


      </xsl:for-each>


    </EmailAddresses>


  </xsl:template>


For the last example, we’ll go one step further and transform the contents of the source XML document to a new format. You can accomplish this by having a LINQ query return a collection of XML Literals. You can use embedded expressions to copy values from the source document into elements and attributes of the new XML Literal. For this example, the transformed document will rename the <EmailAddress> element to <Email>, and include the value of the <EmailPromotion> element as an attribute of the transformed <Email> element.


  Dim transformList = <?xml version=1.0?>


                      <EmailPromotionList>


                          <%= From contact In xmlDoc.<Contacts>.<Contact> _


                              Where contact.<EmailPromotion>.Value > 0 _


                              Select <Email


                                       promotion=<%= contact.<EmailPromotion>.Value %>>


                                       <%= contact.<EmailAddress>.Value %>


                                     </Email> %>


                      </EmailPromotionList>


Comparable XSLT code would look like the following.


  <xsl:template match=aw:Contacts>


    <EmailPromotionList>


      <xsl:for-each select=aw:Contact>


        <xsl:if test=aw:EmailPromotion &gt; 0>


          <Email>


            <xsl:attribute name=promotion>


              <xsl:value-of select=aw:EmailPromotion/>


            </xsl:attribute>


            <xsl:value-of select=aw:EmailAddress/>


          </Email>


        </xsl:if>


      </xsl:for-each>


    </EmailPromotionList>


  </xsl:template>


To sum things up, we’ve seen how Visual Basic, XML Literals and LINQ can be used in place of the <xsl:copy-of>, <xsl:for-each>, <xsl:template>, <xsl:if>, <xsl:value-of>, and <xsl:attribute> elements as well as using XML Axis Properties in place of XPath to create a powerful, yet simple tool for XML transformations.


Stay tuned for more…

AdventureWorksContacts.zip