PsychoCoder
PsychoCoder

Reputation: 10755

Parsing XML with VB.NET with LINQ To XML

I have an incoming XML stream from a web service response that looks like so

<?xml version="1.0" encoding="utf-16"?>
<HotelPropertyDescriptionRS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" TimeStamp="2013-12-30T18:49:36" Version="1.14.1">
  <Success xmlns="http://webservices.sabre.com/sabreXML/2003/07" />
  <RoomStay xmlns="http://webservices.sabre.com/sabreXML/2003/07">
    <RoomRates>
      <RoomRate GuaranteeSurchargeRequired="G" IATACharacteristicIdentification="BGGO00" IATAProductIdentification="BLOOMBERG" RPH="001">
        <AdditionalInfo>
          <CancelPolicy Numeric="06" />
          <Commission NonCommission="true">NON COMMISSIONABLE</Commission>
          <Text>BLOOMBERG LP, 0.0 KM, INCLUDES BREAKFAST, INTERNET, WIFI, SEE</Text>
          <Text>RATE RULES DELUXE ROOM, GUEST ROOM, 1 KING OR 2 TWIN/SINGLE BE</Text>
        </AdditionalInfo>
        <Rates>
          <Rate Amount="66.600" CurrencyCode="KWD">
            <AdditionalGuestAmounts>
              <AdditionalGuestAmount MaxExtraPersonsAllowed="0">
                <Charges ExtraPerson="0" />
              </AdditionalGuestAmount>
            </AdditionalGuestAmounts>
            <HotelTotalPricing Amount="76.590">
              <Disclaimer>INCLUDES TAXES AND SURCHARGES</Disclaimer>
              <TotalSurcharges Amount="9.990" />
            </HotelTotalPricing>
          </Rate>
        </Rates>
      </RoomRate>
....

(there will be many RoomRate values returned. I have created a class to hold the values I need to parse from said XML which looks like so

Namespace Classes.Models
    Public Class RoomRate
        Public Property Rate() As String
        Public Property Surcharge() As String
        Public Property TotalPrice() As String
        Public Property CurrencyCode() As String
        Public Property CancellationPolicy() As String
        Public Property Disclaimer() As String
        Public Property Text() As List(Of String)
        Public Property Commission() As String
        Public Property GuaranteeSurchargeRequired() As String
        Public Property IATACharacteristicIdentification() As String
        Public Property IATAProductIdentification() As String
        Public Property RPH() As String
    End Class
End Namespace

Now I'm not looking for someone to write all the code for me, I'm more looking for ideas on which would be the most efficient way to parse this into my class. I can use the XPathDocument and XPathNavigation & XPathNodeIterator classes to do this (not 100% sure how they work yet) but is there a better way to accomplish this task?

EDIT

I have come up with this so far, which should get me the first attributes in the RoomRate element

Return From el As XElement In _xDoc...<RoomRate>
               Select New RoomRate With { _
                   .GuaranteeSurchargeRequired = el...<RoomRate>.@GuaranteeSurchargeRequired, _
                   .IATACharacteristicIdentification = el...<RoomRate>.@IATACharacteristicIdentification, _
                   .IATAProductIdentification = el...<RoomRate>.@IATAProductIdentification, _
                   .RPH = el...<RoomRate>.@RPH}

Now how would I traverse to get say the cancellation policy, room rate and other attributes/elements within this code I provided? Sorry for so many questions, I really want to learn this the right way and be able to do it correctly.

EDIT #2

I think I'm on the right track here:

Return From el As XElement In _xDoc...<RoomRate>
           Select New RoomRate With { _
               .GuaranteeSurchargeRequired = el.@GuaranteeSurchargeRequired, _
               .IATACharacteristicIdentification = el.@IATACharacteristicIdentification, _
               .IATAProductIdentification = el.@IATAProductIdentification, _
               .RPH = el.@RPH, _
               .CancellationPolicy = el...<AdditionalInfo>...<CancellationPolicy>.@Numeric, _
               .Commission = el...<AdditionalInfo>...<Commission>.@NonCommission}

Can someone let me know if I'm on the right track for accomplishing this?

EDIT 3

I have changed my code to this

For Each n As XElement In _xDoc.Elements("RoomRate")
                    _rates.Add(New RoomRate With { _
                               .GuaranteeSurchargeRequired = n.Attribute("GuarateeSurchargeRequired").Value, _
                               .IATACharacteristicIdentification = n.Attribute("IATACharacteristicIdentification").Value, _
                               .IATAProductIdentification = n.Attribute("IATAProductIdentification").Value, _
                               .RPH = n.Attribute("RPH")})
                Next

And while I'm no longer getting any errors, but am not getting any results returned as well.

EDIT 4 I have made the following changes t my code, which should return an IEnumerable(Of RoomRate) but it's returning nothing (this is based on the XML snippet i posted above)

Dim rates = From rate In _xDoc.Descendants("RoomRate")
                Select New RoomRate With { _
                    .GuaranteeSurchargeRequired = rate.Attribute("GuaranteeSurchargeRequired").Value, _
                    .IATACharacteristicIdentification = rate.Attribute("IATACharacteristicIdentification").Value, _
                    .IATAProductIdentification = rate.Attribute("IATAProductIdentification").Value, _
                    .RPH = rate.Attribute("RPH").Value}

That looks like it should work, what am I doing wrong here?

EDIT #5 @Neolisk I made my changes the way you suggested, it now looks like so

For Each n As XElement In _xDoc.Descendants("RoomRate")
    rate = New RoomRate()
    rate.GuaranteeSurchargeRequired = n.Attribute("GuaranteeSurchargeRequired").Value
    rate.IATACharacteristicIdentification = n.Attribute("IATACharacteristicIdentification").Value
    rate.IATAProductIdentification = n.Attribute("IATAProductIdentification")
    _rates.Add(rate)
Next
Return _rates

But it never goes into the loop, it goes straight to Return _rates

EDIT #6

Ok I got it populating the first 3 attributes of the RoomRate element like this

Dim rate As RoomRate
Dim ns As XNamespace = "http://webservices.sabre.com/sabreXML/2003/07"
Dim rate As RoomRate
For Each n As XElement In _xDoc.Descendants(ns + "RoomRate")
    rate = New RoomRate()
    rate.GuaranteeSurchargeRequired = n.Attribute("GuaranteeSurchargeRequired").Value
    rate.IATACharacteristicIdentification = n.Attribute("IATACharacteristicIdentification").Value
    rate.IATAProductIdentification = n.Attribute("IATAProductIdentification").Value
    rate.RPH = n.Attribute("RPH").Value

    _rates.Add(rate)
Next

Now when I try to traverse through the rest of the document I try and get the CancelPolicy value like so

Dim rate As RoomRate
Dim ns As XNamespace = "http://webservices.sabre.com/sabreXML/2003/07"
Dim rate As RoomRate
For Each n As XElement In _xDoc.Descendants(ns + "RoomRate")
    rate = New RoomRate()
    rate.GuaranteeSurchargeRequired = n.Attribute("GuaranteeSurchargeRequired").Value
    rate.IATACharacteristicIdentification = n.Attribute("IATACharacteristicIdentification").Value
    rate.IATAProductIdentification = n.Attribute("IATAProductIdentification").Value
    rate.RPH = n.Attribute("RPH").Value
    rate.CancellationPolicy = n.Element("AdditionalInfo").Element("CancelPolicy").Attribute("Numeric").Value

    _rates.Add(rate)
Next

But adding that last line causes a NullReferenceException. How would I go about going through the XML now?

Upvotes: 0

Views: 2402

Answers (2)

PsychoCoder
PsychoCoder

Reputation: 10755

Got my issue resolved with this code

Public Function ParseRates() As IEnumerable(Of RoomRate)
    Try
        Dim ns As XNamespace = "http://webservices.sabre.com/sabreXML/2003/07"
        Dim rate As RoomRate
        For Each n As XElement In _xDoc.Descendants(ns + "RoomRate")
            _rates.Add(New RoomRate With { _
                        .GuaranteeSurchargeRequired = n.Attribute("GuaranteeSurchargeRequired").Value, _
                        .IATACharacteristicIdentification = n.Attribute("IATACharacteristicIdentification").Value, _
                        .IATAProductIdentification = n.Attribute("IATAProductIdentification").Value, _
                        .RPH = n.Attribute("RPH").Value, _
                        .CancellationPolicy = n.Element(ns + "AdditionalInfo").Element(ns + "CancelPolicy").Attribute("Numeric").Value, _
                        .Commission = n.Element(ns + "AdditionalInfo").Element(ns + "Commission").Attribute("NonCommission").Value, _
                        .Rate = n.Element(ns + "Rates").Element(ns + "Rate").Attribute("Amount").Value, _
                        .CurrencyCode = n.Element(ns + "Rates").Element(ns + "Rate").Attribute("CurrencyCode").Value,
                        .TotalPrice = n.Element(ns + "Rates").Element(ns + "Rate").Element(ns + "HotelTotalPricing").Attribute("Amount").Value, _
                        .Disclaimer = n.Element(ns + "Rates").Element(ns + "Rate").Element(ns + "HotelTotalPricing").Element(ns + "Disclaimer").Value, _
                        .Surcharge = n.Element(ns + "Rates").Element(ns + "Rate").Element(ns + "HotelTotalPricing").Element(ns + "TotalSurcharges").Attribute("Amount").Value})
        Next
        Return _rates
    Catch ex As Exception
        ErrorMessage = ex.Message
        Return Nothing
    End Try
End Function

Maybe someday I'll find a more efficient way to accomplish this

Upvotes: 1

Victor Zakharov
Victor Zakharov

Reputation: 26424

If you've never dealt with LINQ to XML, you may find yourself struggling a bit at the beginning, but this should help you get there faster:

Dim xml As XDocument = XDocument.Parse("...") 'or XDocument.Load("...")

You can also assign a whole XML (or part thereof) into a variable AS IS, to test your concept, i.e. this is 100% valid syntax in VB10+ (~.NET 4.0+):

Dim xml As XDocument = <?xml version="1.0" encoding="utf-16"?>
                       <RoomRates>
                         <RoomRate>
                           <AdditionalInfo>
                             <Text>BLOOMBERG LP, 0.0 KM</Text>
                           </AdditionalInfo>
                           <Rates>
                             <Rate Amount="66.600" CurrencyCode="KWD"></Rate>
                           </Rates>
                         </RoomRate>
                       </RoomRates>

After that, here is what you can do:

Dim ns As XNamespace = "http://webservices.sabre.com/sabreXML/2003/07"
Dim roomRate As XElement = xml.Descendants(ns + "RoomRate").First

Dim additionalInfoText As String = roomRate.Descendants(ns + "Text").First

You can now inspect both roomRate and additionalInfoText in debugger, to see how it worked.

When in doubt about functionality of XDocument/XElement, please see relevant MSDN articles.

Upvotes: 0

Related Questions