views:

97

answers:

2

Please excuse my stupidity, I tend to find the traversing XML overly complicated.

I am using ASP.NET in VB.

I have an XML document which contains all the details of staff in my company...

<staff>
    <staffName>Test Staff</staffName>
    <staffTitle>Slave</staffTitle>
    <staffDepartmentName>Finance</staffDepartmentName>
    <staffOffice>London</staffOffice>
    <staffEmail>[email protected]</staffEmail>
    <staffPhone>0207 123 456</staffPhone>
    <staffNotes>Working hours Mon to Thurs 9.15 - 5.15</staffNotes>
    <staffBio></staffBio>
</staff>

As you can see, some nodes do not always contain data for ever member of staff; only Directors have biographies.

I access the values like this...

For Each staff In ( _
    From matches In myXMLFile.Descendants("staff").Descendants("staffName") _
        Where matches.Nodes(0).ToString.ToLower.Contains(LCase(search)) _
        Order By matches.Value _
        Select matches)
    staffName = staff.Descendants("staffName").Nodes(0).ToString)
    staffTitle = staff.Descendants("staffTitle").Nodes(0).ToString)
    staffOffice = staff.Descendants("staffOffice").Nodes(0).ToString)
    staffEmail = staff.Descendants("staffEmail").Nodes(0).ToString)
    staffPhone = staff.Descendants("staffPhone").Nodes(0).ToString)
    staffNotes = staff.Descendants("staffNotes").Nodes(0).ToString)
    staffBio = staff.Descendants("staffBio").Nodes(0).ToString)

    ' Do something with that data...
Next

Once it gets to staffBio I get an error saying "Object reference not set to an instance of an object." obviously because that node does not exist.

My question is how can I assign the value to a variable even when it is empty without having to do a conditional check before each assignment?

A: 

OK think this is how to do it.

...
staffBio = staff.Descendants("staffBio").ElementAtOrDefault(0).Value.ToString)
...

Using .ElementAtOrDefault(0) instead of .Nodes(0) just returns "" if it's empty or <staffBio>whatever</staffBio> if it is not.

.Value just returns the content of the tags like "whatever" in the example above.

Is this right? Can anyone see any problems with this?

Yeodave
+4  A: 

First, myXMLFile.Descendants("staff").Descendants("staffName") is redundant. Descendants returns all the elements at any level within an XDocument or XElement. So, myXMLFile.Descendants("staffName") would give the same result.

Second, you can just use the Element property and the Value property like this:

staffBio = staff.Element("staffBio").Value

staff will only have one staffBio element, so there's no need to use the Descendants property. Value is a string, so you don't need to call Value.ToString. If the element is empty, then Value will return an empty string, which is what you're looking for!

Third, there is a much better (and I believe more straight forward) way of doing this in VB.NET. Here's a console app that demonstrates how I would do this:

Module Module1

Sub Main()
    Dim myXMLFile = <allStaff>
                        <staff>
                            <staffName>Test Staff</staffName>
                            <staffTitle>Slave</staffTitle>
                            <staffDepartmentName>Finance</staffDepartmentName>
                            <staffOffice>London</staffOffice>
                            <staffEmail>[email protected]</staffEmail>
                            <staffPhone>0207 123 456</staffPhone>
                            <staffNotes>Working hours Mon to Thurs 9.15 - 5.15</staffNotes>
                            <staffBio></staffBio>
                        </staff>
                        <staff>
                            <staffName>Other Staff</staffName>
                            <staffTitle>Master</staffTitle>
                            <staffDepartmentName>IT</staffDepartmentName>
                            <staffOffice>Oxford</staffOffice>
                            <staffEmail>[email protected]</staffEmail>
                            <staffPhone>0207 123 789</staffPhone>
                            <staffNotes></staffNotes>
                            <staffBio>Some guy.</staffBio>
                        </staff>
                    </allStaff>

    Dim search = "Test"

    Dim searchQuery = From staff In myXMLFile...<staff> _
                      Where staff.<staffName>.Value.Contains(search) _
                      Select si = New StaffInfo With {.Name = staff.<staffName>.Value, _
                                                      .Title = staff.<staffTitle>.Value, _
                                                      .Department = staff.<staffDepartmentName>.Value, _
                                                      .Office = staff.<staffOffice>.Value, _
                                                      .Email = staff.<staffEmail>.Value, _
                                                      .Phone = staff.<staffPhone>.Value, _
                                                      .Notes = staff.<staffNotes>.Value, _
                                                      .Bio = staff.<staffBio>.Value}

    For Each staff In searchQuery
        Console.WriteLine("Name: {0}", staff.Name)
        Console.WriteLine("Title: {0}", staff.Title)
        Console.WriteLine("Department: {0}", staff.Department)
        Console.WriteLine("Office: {0}", staff.Office)
        Console.WriteLine("Email: {0}", staff.Email)
        Console.WriteLine("Phone: {0}", staff.Phone)
        Console.WriteLine("Notes: {0}", staff.Notes)
        Console.WriteLine("Bio: {0}", staff.Bio)
        Console.WriteLine()
    Next

    Console.ReadLine()

End Sub

Private Class StaffInfo

    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property

    Private _title As String
    Public Property Title() As String
        Get
            Return _title
        End Get
        Set(ByVal value As String)
            _title = value
        End Set
    End Property

    Private _department As String
    Public Property Department() As String
        Get
            Return _department
        End Get
        Set(ByVal value As String)
            _department = value
        End Set
    End Property

    Private _office As String
    Public Property Office() As String
        Get
            Return _office
        End Get
        Set(ByVal value As String)
            _office = value
        End Set
    End Property

    Private _email As String
    Public Property Email() As String
        Get
            Return _email
        End Get
        Set(ByVal value As String)
            _email = value
        End Set
    End Property

    Private _phone As String
    Public Property Phone() As String
        Get
            Return _phone
        End Get
        Set(ByVal value As String)
            _phone = value
        End Set
    End Property

    Private _notes As String
    Public Property Notes() As String
        Get
            Return _notes
        End Get
        Set(ByVal value As String)
            _notes = value
        End Set
    End Property

    Private _bio As String
    Public Property Bio() As String
        Get
            Return _bio
        End Get
        Set(ByVal value As String)
            _bio = value
        End Set
    End Property

End Class

End Module

If you have a schema (.xsd file) for your XML, then you can import a reference to that xmlns to your VB source file, which will give you intellisense for writing your LINQ to XML queries.

(Edit: A quick way to create a schema is to open up an XML file in Visual Studio and choose Create Schema from the XML menu.)

For more information and help, please check out the "How Do I" video series on LINQ by Beth Massi.

Dennis Palmer
@Yeodave: This is the best possible answer to your question. Extremely complete and utilizes Linq and objects very well.
Otaku
Wow, that is awesome, cheers Dennis! It seems I still have a lot to learn.
Yeodave