VB XML Cookbook, Recipe 2: Descendants and Ancestors (Doug Rothaus)

VB XML Cookbook, Recipe 2: Descendants and Ancestors (Doug Rothaus)

  • Comments 3

This entry in the cookbook shows how you can access descendant and ancestor elements in an XML document using Visual Basic, XML Axis properties, and LINQ to XML objects.

Descendants

Visual Basic provides XML Axis properties that enable you to easily refer to child nodes and attributes. As is often the case with XML, you may need to reference sub-elements that show up in different levels of the XML hierarchy. In that case, you can use the XML Descendant axis property.

The XML Descendant axis property is identified using three periods followed by the XML element that you want to refer to. For example, in the AdventureWorks contacts source document that we used in Recipe 1 (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. The phone numbers in the content of the <AdditionalContactInfo> element can show up anywhere within the value of the element. As a result, you would need to use the XML Descendant axis property to refer to the phone number elements. For example:

Dim xmlDoc = XDocument.Load("AWContacts.xml")

 

' Return all descendant <telephoneNumber> elements

Dim telephoneNumbers = _

  xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:telephoneNumber>

 

The XML descendant axis property returns a collection of all of the matching elements “down” the XML hierarchy. Let’s look at a more complete example. The following Function returns a list of contact names and phone numbers using the XML Descendant axis property.

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

Imports <xmlns:s="sample-output">

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 Recipe2

 

  Public Function GetPhoneNumbers(ByVal xmlDoc As XDocument) As XElement

    Return <s:ContactPhoneNumbers>

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

                 Where contact.<aci:AdditionalContactInfo>...<act:telephoneNumber>.<act:number>.Count > 0 _

                 Select <s:Contact>

                          <s:Name><%= contact.<FirstName>.Value & " " & _

                                      contact.<LastName>.Value %></s:Name>

                          <s:PhoneNumbers>

                            <%= From number In _

                                contact.<aci:AdditionalContactInfo>...<act:telephoneNumber>.<act:number> _

                                Select <s:Number><%= number.Value %></s:Number> _

                            %>

                          </s:PhoneNumbers>

                        </s:Contact> _

             %>

           </s:ContactPhoneNumbers>

  End Function

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:s="sample-output"

  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">

    <s:ContactPhoneNumbers>

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

    </s:ContactPhoneNumbers>

  </xsl:template>

 

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

    <xsl:if test="count(aci:AdditionalContactInfo//act:telephoneNumber/act:number) > 0">

      <s:Contact>

        <s:Name>

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

          <xsl:text> </xsl:text>

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

        </s:Name>

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

      </s:Contact>

    </xsl:if>

  </xsl:template>

 

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

    <s:PhoneNumbers>

      <xsl:for-each select=".//act:telephoneNumber/act:number">

        <s:Number>

          <xsl:value-of select="."/>

        </s:Number>

      </xsl:for-each>

    </s:PhoneNumbers>

  </xsl:template>

 

</xsl:stylesheet>

It is important to note that you should not use the XML Descendant axis property when the location of a sub-element is always the same. Your code will perform much better if you use the XML Child axis property to refer to a specific location in the XML hierarchy than if you use the XML Descendant axis property to search the entire XML hierarchy for matches.

Ancestors

XML Axis properties make searching “down” the XML hierarchy quick and easy. However, what if you need to search “up” the XML hierarchy? In that case, you can use the Ancestors method of the XNode class. Because XML literals expose the functionality of LINQ to XML classes, you can use the additional capabilities of those LINQ to XML classes when the need arises.

The Ancestors() method searches “up” the XML hierarchy for an XML element that matches the supplied element name. The element name that you supply to the Ancestors method, however, must include the XML namespace for the element as well. You can use the GetXmlNamespace operator to return an XML namespace based on a supplied XML prefix. For the default XML namespace, call the GetXmlNamespace operator and don’t pass it a prefix. If you are searching for an element in the same XML namespace as an element that you already have a reference to, you can access the XML namespace of the referenced element using the Name.Namespace property.

For example, the following sample takes a list of <telephoneNumber> elements (actually, any element of type phoneNumberType as identified in the ContactTypes.xsd file) from the AdventureWorks contacts sample document and returns a list of the phone numbers as well as the name of the related contact for the phone number. The sample calls the Ancestors() method to search up the XML hierarchy for the owning <Contact> node.

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

Imports <xmlns:s="sample-output">

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 Recipe2

 

  Public Function GetPhoneList(ByVal numberList As IEnumerable(Of XElement)) As XElement

    Return <s:PhoneNumberList>

             <%= From number In numberList _

                 Let contact = number.Ancestors(GetXmlNamespace() + "Contact") _

                 Select <s:PhoneNumber>

                          <s:Number type=<%= number.Name.LocalName %>>

                            <%= number.<act:number>.Value %>

                          </s:Number>

                          <s:Owner><%= contact.<FirstName>.Value & " " & _

                                       contact.<LastName>.Value %></s:Owner>

                      </s:PhoneNumber> _

             %>

           </s:PhoneNumberList>

  End Function

End Class

In XSLT, the same transform would look similar to 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:s="sample-output"

  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">

    <s:PhoneNumberList>

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

    </s:PhoneNumberList>

  </xsl:template>

 

  <xsl:template match="aw:Contact//act:telephoneNumber">

    <s:PhoneNumber>

      <s:Number>

        <xsl:attribute name="type">

          <xsl:value-of select="name()" />

        </xsl:attribute>

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

      </s:Number>

      <s:Owner>

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

        <xsl:text> </xsl:text>

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

      </s:Owner>

    </s:PhoneNumber>

  </xsl:template>

</xsl:stylesheet>

Leave a Comment
  • Please add 1 and 6 and type the answer here:
  • Post
  • An identity transform in XSLT does just what the name implies: replace the identity of an element or

  • 料理ブックのこのレシピでは、 XML ドキュメント内の子孫要素と先祖要素に Visual Basic 、 XML 軸プロパティ、および LINQ to XML オブジェクトを使用してアクセスする方法を紹介します。

  • XSLT の ID 変換は、その名称が示すとおり、ある要素または属性の ID を新しい ID で置き換えます。 ID 変換は、構造があまり固定されていない XML マークアップを操作するときに特に重要です。ニュース記事の文書構造を考えてみましょう。タイトルが記事内で参照されると、そのタイトルは斜体で強調表示されます。ただし、その記事の

Page 1 of 1 (3 items)