Saturday, October 01, 2005

Compose XML Content with XLinq Expressions and VB 9.0 XML Literals

My XLinqTestProjectVB project expands on the VB 9.0 examples included in Erik Meijer's "XLinq: XML Programming Refactored (The Return Of The Monoids)" presentation at the XML 2005 Conference to be held in Atlanta on November 14 - 18, 2005. The project demonstrates composing well-formed XML documents from Northwind sample data by the use of:

  1. VB 9.0 expressions that use the XLinq API's XDocument, XDeclaration, XDocumentType, XComment and XElement classes.
  2. VB 9.0 expressions that use the XLinq API's XElement class and add elements with calculated values in a specified position in the document.
  3. VB-only XML Literals created by copying and pasting XML content as VB 9.0 code.

You'll be able to download the project's code from the Fawcette site in the near future. (I'll add a link when the code becomes available.) In the meantime, I've provided some VB 9.0 code examples from the project to illustrate how to compose XML documents with XLink expressions as well as VB 9.0 XML literals.

A Simple Sales Order XDocument

The following code composes and displays a static XML document in a text box on the project's Windows form. The document adds instances of the XDocument (container), XDeclaration, XDocumentType, XComment, and XProcessingInstruction classes to the composed XElement instances. (XDeclaration and XDocumentType weren't finished when Microsoft released the initial Technology Preview.)

(To test the code, create a LINQ Windows form project, add a txtXML multiline text box to the form, paste the code for the SalesOrderDocument method to the form, and execute it with a button or from the Form1_Load event handler.)

Private Sub SalesOrderDocument()
   'Create a static SalesOrders XDocument
   'from XLinq expressions

   'Define a static BillTo group
   Dim expBillTo = _
   New XElement("BillTo", _
   New XAttribute("CustomerID", "RATTC"), _
   New XElement("CompanyName", _
    "Rattlesnake Canyon Grocery"), _
   New XElement("ContactName", _
    "Paula Wilson"), _
   New XElement("ContactTitle", _
    "Assistant Sales Representative"), _
   New XElement("Address", "2817 Milton Dr."), _
   New XElement("City", "Albuquerque"), _
   New XElement("Region", "NM"), _
   New XElement("PostalCode", "87110"), _
   New XElement("Country", "USA"), _
   New XElement("Phone", "(505) 555-5939"), _
   New XElement("Fax", "(505) 555-3620"))
   '
   'Define a static ShipTo group
   Dim expShipTo = _
   New XElement("ShipTo", _
   New XElement("ShipName", _
     "Rattlesnake Canyon Grocery"), _
   New XElement("ShipAddress", _
     "2821 Milton Dr."), _
   New XElement("ShipCity", "Albuquerque"), _
   New XElement("ShipRegion", "NM"), _
   New XElement("ShipPostalCode", "87110"), _
   New XElement("ShipCountry", "USA"))
   '
   'Define three static LineItem groups
   Dim expLineItem1 = _
   New XElement("LineItem", _
   New XAttribute("OrderID", 10262), _
   New XElement("ProductID", 5), _
   New XElement("UnitPrice", 17.0), _
   New XElement("Quantity", 12), _
   New XElement("Discount", 0.2))
   '
   Dim expLineItem2 = _
   New XElement("LineItem", _
   New XAttribute("OrderID", 10262), _
   New XElement("ProductID", 7), _
   New XElement("UnitPrice", 24.0), _
   New XElement("Quantity", 15), _
   New XElement("Discount", 0.0))
   '
   Dim expLineItem3 = _
   New XElement("LineItem", _
   New XElement("Quantity", 2), _
   New XAttribute("OrderID", 10262), _
   New XElement("ProductID", 56), _
   New XElement("UnitPrice", 30.4), _
   New XElement("Discount", 0.0))
   '
   'Create a SalesOrder document
   Dim expOrder = _
   New XDocument( _
   New XDeclaration("1.0", "UTF-8", "yes"), _
   New XDocumentType(), _
   New XComment("XElement Demo"), _
   New XProcessingInstruction("XLinkProject", _
     "ProcessingInstructions"), _
   New XElement("SalesOrder", _
   New XAttribute("OrderID", 10262), _
   New XAttribute("EmployeeID", 8), _
   New XAttribute("ShipVia", 3), _
   expBillTo, _
   New XElement("OrderDate", "1996-7-22"), _
   New XElement("RequiredDate", "1996-8-19"), _
   New XElement("ShipDate", "1996-7-25"), _
   New XElement("Freight", 48.29), _
   expShipTo, _
   New XElement("LineItems", _
   expLineItem1, expLineItem2, expLineItem3)))
   '
   'Show formatted document in the text box
   txtXML.Text = expOrder.ToString
End Sub
This figure shows the project's form displaying the first part of the XML document composed by executing the preceding method.

XLinq overloads the Object.ToString method to display the XML document formatted with 1-character indents and CR/LF pairs.

A Sales Order XElement with Added XElement Groups

The following two methods create a simple Sales Order document from XElements and then add after the LineItems group a Summary group with calculated order totals.

(If you copy the following code to a project, don't forget to copy and paste the expressions that define the expBillTo, expShipTo, and three expLineItem# groups.)

Private Sub SalesOrderExpression()
   'Create a static SalesOrders XElement
   'from XLinq expressions
   '
   'Add expBillTo, expShipTo,
   'and expLineItem1...3 from above
   '
   'Create a SalesOrder parent XElement
   Dim expOrder = _
   New XElement("SalesOrder", _
   New XAttribute("OrderID", 10262), _
   New XAttribute("EmployeeID", 8), _
   New XAttribute("ShipVia", 3), _
     expBillTo, _
   New XElement("OrderDate", "1996-7-22"), _
   New XElement("RequiredDate", "1996-8-19"), _
   New XElement("ShipDate", "1996-7-25"), _
   New XElement("Freight", 48.29), _
     expShipTo, _
   New XElement("LineItems", _
     expLineItem1, expLineItem2, expLineItem3))
   '
   'Add calculated items and order total after
   'the element
   Dim decFreight As Decimal = 48.29D
   Dim xeLineItems As XElement = _
     expOrder.Element("LineItems")
   xeLineItems.AddAfterThis(New _
     XElement("Summary", _
   New XElement("Items", _
     SalesOrderTotal(expOrder)), _
   New XElement("Freight", decFreight), _
   New XElement("OrderTotal", _
     SalesOrderTotal(expOrder) + decFreight)))
   '
   txtXML.Text = expOrder.ToString
End Sub
'
Function SalesOrderTotal(ByVal xeSO _
  As XElement) As Decimal
   'Calculate LineItems discounted total
   Dim decTotal As Decimal
   For Each xeItem As XElement In _
     xeSO.Descendants("LineItem")
      Dim decPrice As Decimal = _
        CDec(xeItem.Element("UnitPrice"))
      Dim decQuantity As Integer = _
        CInt(xeItem.Element("Quantity"))
      Dim decDiscount As Decimal = _
        CDec(xeItem.Element("Discount"))
      decTotal += decQuantity * decPrice * _
        (1 - decDiscount)
   Next
   Return Math.Round(decTotal, 2)
End Function
The following figure shows the last part of the XML document composed by the preceding two methods. The added Summary group with calculated element values is highlighted.

Composing XML Documents with VB 9.0 XML Literals

VB 9.0 lets you compose XML documents with XML Literals, which you can copy and paste from a source document to a VB 9.0 method. The following code creates a document similar to the preceding, but your code formatting controls formatting of the resulting XML document. Notice the ASP.NET-like syntax for inserting named XML literals.

    Private Sub SalesOrderLiteral()
        'Copy and paste XML content to create
        'Visual Basic XML literals
        '
        Dim litBillTo = _
  <BillTo CustomerID="RATTC">
    <CompanyName>
      Rattlesnake Canyon Grocery
    </CompanyName>
    <ContactName>Paula Wilson</ContactName>
    <ContactTitle>
      Assistant Sales Representative
    </ContactTitle>
    <Address>2817 Milton Dr.</Address>
    <City>Albuquerque</City>
    <Region>NM</Region>
    <PostalCode>87110</PostalCode>
    <Country>USA</Country>
    <Phone>(505) 555-5939</Phone>
    <Fax>(505) 555-3620</Fax>
  </BillTo>
        '
        Dim litShipTo = _
  <ShipTo>
    <ShipName>Rattlesnake Canyon Grocery</ShipName>
    <ShipAddress>2821 Milton Dr.</ShipAddress>
    <ShipCity>Albuquerque</ShipCity>
    <ShipRegion>NM</ShipRegion>
    <ShipPostalCode>87110</ShipPostalCode>
    <ShipCountry>USA</ShipCountry>
  </ShipTo>
        '
        Dim litLineItem1 = _
    <LineItem OrderID="10262">
      <ProductID>5</ProductID>
      <UnitPrice>17</UnitPrice>
      <Quantity>12</Quantity>
      <Discount>0.2</Discount>
    </LineItem>
        Dim litLineItem2 = _
    <LineItem OrderID="10262">
      <ProductID>7</ProductID>
      <UnitPrice>24</UnitPrice>
      <Quantity>15</Quantity>
      <Discount>0</Discount>
    </LineItem>
        Dim litLineItem3 = _
    <LineItem OrderID="10262">
      <Quantity>2</Quantity>
      <ProductID>56</ProductID>
      <UnitPrice>30.4</UnitPrice>
      <Discount>0</Discount>
    </LineItem>
        '
        Dim litOrder = _
<SalesOrder OrderID="10262" EmployeeID="8"
  ShipVia="3">
  <%= litBillTo %>
  <OrderDate>1996-7-22</OrderDate>
  <RequiredDate>1996-8-19</RequiredDate>
  <ShipDate>1996-7-25</ShipDate>
  <Freight>48.29</Freight>
  <%= litShipTo %>
  <LineItems>
    <%= litLineItem1 %>
    <%= litLineItem2 %>
    <%= litLineItem3 %>
  </LineItems>
</SalesOrder>
  '
  'Add calculated items after </LineItems>
  Dim xeLineItems As XElement = _
    litOrder.Element("LineItems")
  xeLineItems.AddAfterThis(New _
    XElement("Summary", _
  New XElement("Items", _
    SalesOrderTotal(litOrder)), _
  New XElement("Freight", 48.29), _
  New XElement("OrderTotal", _
  SalesOrderTotal(litOrder) + 48.29)))
  '
  'Display the formatted document
  '(with fix for Lf -> CrLf)
  txtXML.Text = Replace(litOrder.ToString, _
    vbLf, vbCrLf)
  txtXML.Text = Replace(txtXML.Text, "><", _
    ">" + vbCrLf + "<")
    End Sub

The need for a fixup to convert vbLF (newline) characters to conventional vbCrLf (carriage return/line feed) pairs undoubtedly is an oversight in the VB 9.0 technology preview release.

The following figure shows the document composed by the following method, with assistance from the previously added SaleOrderTotal method.

Notice that indents are two characters because the individual element groups have two-character indents.

Update 10/11/2005: Microsoft MVP Don Demsak (a.k.a. DonXML) draws an analogy between VB XLinq literals and "classic ASP" with his XAML + XLinq + VB.Net's XML Literals Equals Classic ASP For WinForms? post.

--rj

Technorati: