Search code examples
jsonxmlvb.net

Looping through an XML file containing nested tags


I'm writing some code that'll allow me to control a bands light by sending a MIDI note to a VB.net program. The setlist and structure of each song is defined in an XML file as well as the lighting sequence to use in each section of the song (verse, chorus, outro etc.). The XML file looks like this:

<?xml version="1.0" standalone="yes"?>
<setlist>
    <song>
        <title>Blink 182 - All the Small Things</title>
        <section id = "0" name = "intro"  part = "SCENE0"></section>
        <section id = "1" name = "verse"  part = "SCENE1"></section>
        <section id = "2" name = "chorus" part = "SCENE2"></section>
        <section id = "3" name = "verse"  part = "SCENE3"></section>
        <section id = "4" name = "solo"   part = "SCENE4"></section>
        <section id = "5" name = "chorus" part = "SCENE5"></section>
        <section id = "6" name = "outro"  part = "SCENE6"></section>
        <section id = "7" name = "hold"   part = "SCENE7"></section>
    </song>
    <song>
        <title>Green Day - Holiday</title>
        <section id = "0" name = "intro"  part = "SCENE0"></section>
        <section id = "1" name = "verse"  part = "SCENE3"></section>
        <section id = "2" name = "chorus" part = "SCENE2"></section>
        <section id = "3" name = "verse"  part = "SCENE4"></section>
        <section id = "4" name = "solo"   part = "SCENE5"></section>
        <section id = "5" name = "outro"  part = "SCENE6"></section>
        <section id = "6" name = "hold"   part = "SCENE7"></section>
    </song>
    <song>
        <title>Paramore - Still into you</title>
        <section id = "0" name = "intro"  part = "SCENE0"></section>
        <section id = "1" name = "verse"  part = "SCENE3"></section>
        <section id = "2" name = "chorus" part = "SCENE2"></section>
        <section id = "3" name = "verse"  part = "SCENE4"></section>
        <section id = "4" name = "hold"   part = "SCENE7"></section>
    </song>
</setlist>

I tried the following code which converts the XML file into a JSON array and then loops through it.

What I'm currently trying to get is the number of songs and the number of sections in each, e.g., song 1 8 sections, song 2 7 sections and song 3 5 sections. These will be stored in an array for later use.

This is my VB function:

Private Sub readSetList(setListFile As String)
    ' Read setlist data
    Dim setlist As New XmlDocument()
    setlist.Load(setListFile)

    ' Convert to JSON
    Dim JsonString As String = JsonConvert.SerializeXmlNode(setlist)

    ' Convert to an array
    Dim JsonArray As JObject = JObject.Parse(JsonString)

    Dim songCount As Integer = 0
    Dim sectionCount As Integer = 0

    For Each item As JProperty In JsonArray.Item("setlist")
        Dim itemObjects As JToken = item.Value
        For Each i As JObject In itemObjects
            For Each p In i
                ' If the key is 'title' then this is a new song so increment the song counter
                If p.Key.ToString = "title" Then
                    songCount += 1
                    Debug.Print(songCount & " => Title: " & p.Value.ToString)
                End If

                If p.Key.ToString = "section" Then
                    sectionCount += 1
                    Debug.Print(sectionCount & " => Section: " & p.Value.ToString)
                End If
            Next
        Next
    Next
End Sub

Whilst this successfully counts the number of songs, I am looking for a way of looping through the section tags and retrieving their properties. following function outputs JSON objects with one section tag:

3 => Title: Paramore - Still into you
3 => Section: [
  {
    "@id": "0",
    "@name": "intro",
    "@part": "SCENE0"
  },
  {
    "@id": "1",
    "@name": "verse",
    "@part": "SCENE3"
  },
  //...

How do I count the number of section tags and (ideally) access the name and part attributes?


Solution

  • This can be done using XPATH expressions in System.Xml:

    Imports System.Xml
    
    Module Program
      Sub Main(args As String())
        Dim setList = New XmlDocument
        setList.Load("songlist.xml")
        Dim songs = setList.DocumentElement.SelectNodes("/setlist/song")
        Console.WriteLine($"{songs.Count} song(s)")
        For Each song As XmlNode In songs
          Dim title As String = song.SelectSingleNode("title").FirstChild.Value 'retrieve contents of title node
          Dim sections = song.SelectNodes("section")
          Dim fifthSectionPart = sections(4).SelectSingleNode("@part").Value 'syntax to retrieve an attribute
          Console.WriteLine($"'{title}' has {sections.Count} sections, the fifth section's part is called '{fifthSectionPart}'")
        Next song
      End Sub
    End Module
    

    Output:

    3 song(s)
    'Blink 182 - All the Small Things' has 8 sections, the fifth section's part is called 'SCENE4'
    'Green Day - Holiday' has 7 sections, the fifth section's part is called 'SCENE5'
    'Paramore - Still into you' has 5 sections, the fifth section's part is called 'SCENE7'