Home > Back-end >  Recursing through a nested XML
Recursing through a nested XML

Time:07-16

I have the following XML file:

<Tournament TeamPlayers="1">
    <Teams>
        <Team>
            <TeamID>0</TeamID>
            <TeamName>Sample</TeamName>
            <Status>10</Status>
            <Memo>Sample Team</Memo>
            <ByeRounds>0</ByeRounds>
            <Players>
                <Player>
                    <MemberNumber>1</MemberNumber>
                    <MemberName>Dummy</MemberName>
                    <PlayerFirstName>Test</PlayerFirstName>
                    <PlayerLastName>User</PlayerLastName>
                    <SeatOrder>A</SeatOrder>
                </Player>
                <Player>
                    <MemberNumber></MemberNumber>
                    <MemberName></MemberName>
                    <PlayerFirstName></PlayerFirstName>
                    <PlayerLastName></PlayerLastName>
                    <SeatOrder>B</SeatOrder>
                </Player>
                <Player>
                    <MemberNumber></MemberNumber>
                    <MemberName></MemberName>
                    <PlayerFirstName></PlayerFirstName>
                    <PlayerLastName></PlayerLastName>
                    <SeatOrder>C</SeatOrder>
                </Player>
            </Players>
        </Team>
    </Teams>
</Tournament>

The idea is that I recursively loop through the <Players> block and add the data there to a holder of class data. However, when I run the following code, which looks like it is yielding the contents of the Inner Players field, VS stops warning that it is empty:

    Dim teamNodes As XmlNodeList = xmlDoc.DocumentElement.SelectNodes("/Tournament/Teams/Team")
    Dim lstOutputTeams As New List(Of TournamentTeam)

    ' Check the atrribute of "TeamPlayers" in the root. 
    'Dim intExpectedPlayers As Integer = xmlDoc.Attributes("/").Value
    Dim intExpectedPlayers As Integer = xmlDoc.SelectSingleNode("/Tournament").Attributes("TeamPlayers").Value

    For Each node As XmlNode In teamNodes

        Dim tpPlayerA As New PlayerInfo
        Dim tpPlayerB As New PlayerInfo
        Dim tpPlayerC As New PlayerInfo

        Dim playerNodes As XmlNodeList = node.SelectNodes("Players")

        For Each innerNode As XmlNode In playerNodes
            Dim tpPlayer As PlayerInfo = New PlayerInfo With {
                .strMembershipName = innerNode.SelectSingleNode("MemberName").InnerText,
                .strMembershipNo = innerNode.SelectSingleNode("MemberNumber").InnerText,
                .strPlayerFirstName = innerNode.SelectSingleNode("PlayerFirstName").InnerText,
                .strPlayerLastName = innerNode.SelectSingleNode("PlayerLastName").InnerText,
                .strSeatOrder = innerNode.SelectSingleNode("SeatOrder").InnerText
            }

            Select Case tpPlayer.strSeatOrder
                Case "A"
                    tpPlayerA = tpPlayer
                Case "B"
                    tpPlayerB = tpPlayerB
                Case "C"
                    tpPlayerC = tpPlayerC
            End Select
        Next

        lstOutputTeams.Add(New TournamentTeam() With {
                           .strTeamName = node.SelectSingleNode("TeamName").InnerText,
                           .intTeamID = node.SelectSingleNode("TeamID").InnerText,
                           .intByeRounds = node.SelectSingleNode("ByeRounds").InnerText,
                           .intStatus = node.SelectSingleNode("Status").InnerText,
                           .strMemo = node.SelectSingleNode("Memo").InnerText,
                           .tpPlayerA = tpPlayerA,
                           .tpPlayerB = tpPlayerB,
                           .tpPlayerC = tpPlayerC}
                           )
    Next

In particular, the error is: System.NullReferenceException: 'Object reference not set to an instance of an object.'. I'm not sure where the null reference is taking place. (The breakpoint places it at the Dim tpPlayer As Player Info line.

CodePudding user response:

It may be causing you grief that your Select Case is assigning variables to themselves:

Select Case tpPlayer.strSeatOrder
    Case "A"
        tpPlayerA = tpPlayer
    Case "B"
        tpPlayerB = tpPlayerB
    Case "C"
        tpPlayerC = tpPlayerC
End Select

The RHS of each should just be tpPlayer.

You caused this bug by doing this:

    Dim tpPlayerA As New PlayerInfo
    Dim tpPlayerB As New PlayerInfo
    Dim tpPlayerC As New PlayerInfo

You should have done this instead:

    Dim tpPlayerA As PlayerInfo
    Dim tpPlayerB As PlayerInfo
    Dim tpPlayerC As PlayerInfo

Here's what your code would look like with XDocument. It's much cleaner.

    Dim intExpectedPlayers As Integer = CInt(xd.Root.Attribute("TeamPlayers"))
    
    Dim lstOutputTeams As List(Of TournamentTeam) = _
    ( _
        From team In xd.Root.Element("Teams").Elements("Team") _
        Let players = team.Element("Players").Elements("Player").Select(Function(player) _
            New PlayerInfo() With _
            {
                .strMembershipName = player.Element("MemberName").Value,
                .strMembershipNo = player.Element("MemberNumber").Value,
                .strPlayerFirstName = player.Element("PlayerFirstName").Value,
                .strPlayerLastName = player.Element("PlayerLastName").Value,
                .strSeatOrder = player.Element("SeatOrder").Value
            }).ToDictionary(Function(x) x.strSeatOrder) _
        Select New TournamentTeam() With _
        {
            .intTeamID = CInt(team.Element("TeamID")),
            .strTeamName = CStr(team.Element("TeamName")),
            .intStatus = CInt(team.Element("Status")),
            .strMemo = CStr(team.Element("Memo")),
            .intByeRounds = CInt(team.Element("ByeRounds")),
            .tpPlayerA = players("A"),
            .tpPlayerB = players("B"),
            .tpPlayerC = players("C")
        } _
    ).ToList()

Here's the creation of the xd that I used for testing:

    Dim xd = XDocument.Parse("<Tournament TeamPlayers=""1"">
    <Teams>
        <Team>
            <TeamID>0</TeamID>
            <TeamName>Sample</TeamName>
            <Status>10</Status>
            <Memo>Sample Team</Memo>
            <ByeRounds>0</ByeRounds>
            <Players>
                <Player>
                    <MemberNumber>1</MemberNumber>
                    <MemberName>Dummy</MemberName>
                    <PlayerFirstName>Test</PlayerFirstName>
                    <PlayerLastName>User</PlayerLastName>
                    <SeatOrder>A</SeatOrder>
                </Player>
                <Player>
                    <MemberNumber></MemberNumber>
                    <MemberName></MemberName>
                    <PlayerFirstName></PlayerFirstName>
                    <PlayerLastName></PlayerLastName>
                    <SeatOrder>B</SeatOrder>
                </Player>
                <Player>
                    <MemberNumber></MemberNumber>
                    <MemberName></MemberName>
                    <PlayerFirstName></PlayerFirstName>
                    <PlayerLastName></PlayerLastName>
                    <SeatOrder>C</SeatOrder>
                </Player>
            </Players>
        </Team>
    </Teams>
</Tournament>")

I'd also suggest changing your classes to use the standard naming conventions for .NET.

Public Class TournamentTeam
    Public Name As String
    Public ID As Integer
    Public ByeRounds As Integer
    Public Status As Integer
    Public Memo As String
    Public PlayerA As PlayerInfo
    Public PlayerB As PlayerInfo
    Public PlayerC As PlayerInfo
End Class

Public Class PlayerInfo
    Public MembershipName As String
    Public MembershipNo As String
    Public FirstName As String
    Public LastName As String
    Public SeatOrder As String
End Class

Finally, I'd also look at removing the three Player properties and put a dictionary in instead.

If it's always A, B, and C then use an Enum as the key, otherwise use a String.

Public Class TournamentTeam
    Public Name As String
    Public ID As Integer
    Public ByeRounds As Integer
    Public Status As Integer
    Public Memo As String
    Public Players As Dictionary(Of PlayerSeat, PlayerInfo)
End Class

Public Enum PlayerSeat
    A
    B
    C
End Enum

Public Class PlayerInfo
    Public MembershipName As String
    Public MembershipNo As String
    Public FirstName As String
    Public LastName As String
    Public SeatOrder As String
End Class

Then your code would be:

    Dim lstOutputTeams As List(Of TournamentTeam) = _
    ( _
        From team In xd.Root.Element("Teams").Elements("Team") _
        Select New TournamentTeam() With _
        {
            .ID = CInt(team.Element("TeamID")),
            .Name = CStr(team.Element("TeamName")),
            .Status = CInt(team.Element("Status")),
            .Memo = CStr(team.Element("Memo")),
            .ByeRounds = CInt(team.Element("ByeRounds")),
            .Players = team.Element("Players").Elements("Player").Select(Function(player) _
            New PlayerInfo() With _
            {
                .MembershipName = player.Element("MemberName").Value,
                .MembershipNo = player.Element("MemberNumber").Value,
                .FirstName = player.Element("PlayerFirstName").Value,
                .LastName = player.Element("PlayerLastName").Value,
                .SeatOrder = player.Element("SeatOrder").Value
            }).ToDictionary(Function(x) CType([Enum].Parse(GetType(PlayerSeat), x.SeatOrder), PlayerSeat))
        } _
    ).ToList()
  • Related