VB XML Cookbook, Recipe 3: Identity Transforms (Doug Rothaus)

Published 25 April 08 11:19 AM

An identity transform in XSLT does just what the name implies: replace the identity of an element or attribute with a new identity. Identity transforms are especially critical when dealing with XML markup that is less rigid in its structure. Consider a documentation structure for news articles. Whenever a title is referred to within an article, it is highlighted with italics. However, the XML schema for the article does not specify italics, it specifies that the term is some type of title so that different transforms can handle the term in their own fashion. When you transform XML such as this into a readable format, such as HTML, you need to preserve the structure of the text around the highlighted term so that the original intent remains intact. That is…

<Paragraph sequenceID=”1”>This week a remastered version of the movie <title type=”movie”>Raider’s of the Lost Ark</title> was released.</Paragraph>

becomes…

<p>This week a remastered version of the movie <i>Raider’s of the Lost Ark</i> was released.</p>

In a browser you see…

This week a remastered version of the movie Raider’s of the Lost Ark was released.

(Note: I have no idea if they are really releasing a remastered version of Raider’s of the Lost Ark, I just typed the first thing that popped into my head).

You can perform an identity transform using Visual Basic and XML Literals by combining XML Axis properties and the ReplaceWith method for LINQ to XML objects.

Let’s look at a more complete example. In the AdventureWorks contacts source document that we used in previous recipes (you can download the XML document and related schemas from the Recipe 1 post), there is an <AdditionalContactInfo> element that contains information about the contact such as phone numbers, shipping and billing addresses, and so on. To keep things simple, we’ll just look at the <eMail> element. The <eMail> element can show up in a number of places in the contents of the <AdditionalContactInfo> element. As a result, you can use the XML Descendant axis property discussed in Recipe 2 to retrieve all of the references to <eMail> elements. You can then iterate through the query results and call the ReplaceWith method for each <eMail> element and replace it with a new identity. For example:

  Dim emails = (From email In _

    xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()

 

  For Each email In emails

    TransformEmail(email)

  Next

In this example, the output is HTML and the <eMail> element is replaced with an anchor element that specifies a mailto: link to the e-mail address. The previous code snippet passes the <eMail> XElement object to the function TransformEmail, which does the actual replacing.

  Private Sub TransformEmail(ByVal email As XElement)

    Dim emailHtml = <div class="Email">

                      <a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>

                        <%= email.<act:eMailAddress>.Value %>

                      </a>

                    </div>

 

    email.ReplaceWith(emailHtml)

  End Sub

 

A simple class that creates the HTML document using these samples is shown here.

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

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

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

 

Public Class Recipe3

 

  Public Function GetContactsHtml(ByVal xmlDoc As XDocument) As XElement

    ' Replace e-mail address tags with mailto links.

    Dim emails = (From email In xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()

 

    For Each email In emails

      TransformEmail(email)

    Next

 

    ' Create the HTML document

    Return <html>

             <body>

               <table border="1">

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

                     Select <tr>

                              <td valign="top">

                                <%= contact.<FirstName>.Value & " " & contact.<LastName>.Value %>

                              </td>

                              <td valign="top">

                                <%= contact.<aci:AdditionalContactInfo> %>

                              </td>

                            </tr> _

                 %>

               </table>

             </body>

           </html>

  End Function

 

 

  Private Sub TransformEmail(ByVal email As XElement)

    Dim emailHtml = <div class="Email">

                      <a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>

                        <%= email.<act:eMailAddress>.Value %>

                      </a>

                    </div>

    email.ReplaceWith(emailHtml)

  End Sub

End Class

 

In XSLT, the same transform would look like the following:

<?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:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">

  <xsl:output method="html" indent="yes"/>

  <xsl:template match="aw:Contacts">

    <html>

       <body>

         <table border="1">

           <xsl:apply-templates select="aw:Contact" />

         </table>

      </body>

    </html>

  </xsl:template>

 

  <xsl:template match="aw:Contact">

    <tr>

      <td valign="top">

        <xsl:value-of select="aw:FirstName"/>

        <xsl:text> </xsl:text>

        <xsl:value-of select="aw:LastName"/>

      </td>

      <td valign="top">

        <xsl:apply-templates select="aci:AdditionalContactInfo" />

      </td>

    </tr>

  </xsl:template>

 

  <xsl:template match="aci:AdditionalContactInfo">

    <div class="AdditionalInfo">

      <xsl:copy>

        <xsl:apply-templates select="@* | node()" />

      </xsl:copy>

    </div>

  </xsl:template>

 

  <xsl:template match="act:eMail">

      <div class="Email">

        <a>

          <xsl:attribute name="href">

            <xsl:text>mailto:</xsl:text>

            <xsl:value-of select="act:eMailAddress" />

          </xsl:attribute>

          <xsl:value-of select="act:eMailAddress" />

        </a>

      </div>

  </xsl:template>

</xsl:stylesheet>

 

Grouping by Element Type

One thing that you can take advantage of in Visual Basic is the ability to group transforms by element type. For example, the ContactTypes.xsd file that we have been using in our AdventureWorks samples defines an address type, and e-mail type, and a phone number type. ContactTypes.xsd also identifies three different element names that are the address type and five different element names that are the phone number type. In XSLT, you would create a template for each address type and end up with three different templates for information that is, essentially, formatted the same. In Visual Basic, you can easily create a query for all different address or phone number elements and pass that to the same VB function, which acts as your XSLT template. For example:

    Dim addresses = (From addr In info...<act:homePostalAddress>).Union( _

                     From addr In info...<act:physicalDeliveryOfficeName>).Union( _

                     From addr In info...<act:registeredAddress>).ToList()

 

    For Each address In addresses

      TransformAddress(address)

    Next

 

    ...

 

    Private Sub TransformAddress(ByVal address As XElement)

      Dim addressHtml = _

        <div class="Address">

          <%= address.<act:Street>.Value %><br/>

          <%= address.<act:City>.Value & ", " %>&#32;

          <%= address.<act:StateProvince>.Value %>&#32;

          <%= address.<act:PostalCode>.Value %><br/>

          <%= address.<act:CountryRegion>.Value %><br/>

          <%= _

            GetSpecialInstructions(address.<act:SpecialInstructions>.ToList()) %>

        </div>

 

      address.ReplaceWith(addressHtml)

    End Sub

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Airline Travel &raquo; VB XML Cookbook, Recipe 3: Identity Transforms (Doug Rothaus) said on April 25, 2008 4:33 PM:

PingBack from http://www.travel-hilarity.com/airline_travel/?p=4129

# The Visual Basic Team said on May 8, 2008 12:42 PM:

Recipe 3 showed one way to work with mixed XML content using the XML Descendant axis property and the

Leave a Comment

(required) 
(optional)
(required) 

This Blog

Syndication

Page view tracker