XML : XML: How to parse 2 levels of nested List items?

I am trying to parse an xml file that contains info and lyrics about songs. Below is a complete sample XML file.

  <?xml version='1.0' encoding='utf-8'?>  <song xmlns="http://openlyrics.info/namespace/2009/song" version="0.8" createdIn="OpenLP 1.9.9" modifiedIn="OpenLP 1.9.9" modifiedDate="2015-12-02T00:52:19">    <properties>      <titles>        <title>_Les Test Song</title>        <title>alternate title here</title>      </titles>      <comments>        <comment>some comments or notes here</comment>      </comments>      <copyright>©some statement here</copyright>      <verseOrder>i1 v1 c1 b1 p1 e1 o1</verseOrder>      <ccliNo>123456789</ccliNo>      <authors>        <author>Author Unknown</author>      </authors>      <songbooks>        <songbook name="Hymnal" entry="2"/>      </songbooks>    </properties>    <lyrics>      <verse name="v1">        <lines>verse one lyrics</lines>      </verse>      <verse name="c1">        <lines>Chorus 1 lyrics</lines>      </verse>      <verse name="b1">        <lines>Bridge 1 lyrics</lines>        <lines>line 2</lines>        <lines>line 0</lines>      </verse>      <verse name="p1">        <lines>Pre-chorus lyrics</lines>      </verse>      <verse name="i1">        <lines>intro lyrics</lines>      </verse>      <verse name="e1">        <lines>Ending lyrics</lines>      </verse>      <verse name="o1">        <lines>Other lyrics</lines>      </verse>    </lyrics>  </song>    

Some things to note about the file contents:

  1. The file uses 1 namespace
  2. There are 2 main nodes within the primary <song> node named <properties> and <lyrics>
  3. The first "main" node <properties> contains metadata about the song
  4. The second "main" node <lyrics> contains a list of verses and their lyrics
  5. Within each <lyrics> node, there are 1 or more verses tagged as <verse>
  6. Within each <verse>, there are 1 or more lines to the verse tagged as <lines>
  7. I need to preserve the relationships of lines to verses to each song
  8. To restate the basic structure, a song has metadata and 1 or more verses, and a verse has 1 or more lines

My thoughts are to grab the song's metadata (author, title, etc.), then create a List of verses using a foreach loop, and within this loop, create a nested List of lines using another foreach loop.

I am open to using any technique, however, I have spent time trying both XMLDocument class and the XDocument class with Linq.

Here is what I have done so far with the XMLDocument class. My classes:

  using System;  using System.Collections.Generic;  using System.Xml;  using System.IO;    namespace openlpimport  {      public class Song      {          public string title { get; set; }          public string author { get; set; }          public string copyright { get; set; }          public string ccli { get; set; }          public string hymnal { get; set; }          public string notes { get; set; }          public string playorder { get; set; }          public List<Verse> verseList { get; set; }            public Song()          {              verseList = new List<Verse>();          }      }        public class Verse      {          public string verseName { get; set; }          public List<Lyric> lyricList { get; set; }            public Verse()          {              lyricList = new List<Lyric>();          }      }        public class Lyric      {          public string verseLyric { get; set; }            public Lyric()          {              verseLyric = "N/A";          }      }    

Here is what I have for code:

      public class XMLParser      {          public static Song ParseByXMLDocument()          {              var thisSong = new Song();              XmlDocument xmlDoc = new XmlDocument();              xmlDoc.Load(@"C:\\Users\Les\Documents\XML2TXT\_Les Test Song (Author Unknown).xml");              XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);              nsMgr.AddNamespace("ns", "http://openlyrics.info/namespace/2009/song");              XmlNode SongTitleNode = xmlDoc.SelectSingleNode("//ns:title", nsMgr);              thisSong.title = SongTitleNode.InnerText;              XmlNode SongAuthorNode = xmlDoc.SelectSingleNode("//ns:author", nsMgr);              thisSong.author = SongAuthorNode.InnerText;              XmlNode SongCopyrightNode = xmlDoc.SelectSingleNode("//ns:copyright", nsMgr);              thisSong.copyright = SongCopyrightNode.InnerText;              XmlNode SongCCLINode = xmlDoc.SelectSingleNode("//ns:ccliNo", nsMgr);              thisSong.ccli = SongCCLINode.InnerText;              XmlNode SongHymnalNode = xmlDoc.SelectSingleNode("//ns:songbook", nsMgr);              thisSong.hymnal = SongHymnalNode.Attributes.GetNamedItem("entry").Value;              XmlNode SongNotesNode = xmlDoc.SelectSingleNode("//ns:comment", nsMgr);              thisSong.notes = SongNotesNode.InnerText;  //  Define nodes to loop through Song Verses              XmlNodeList VerseListNode = xmlDoc.SelectNodes("//ns:verse", nsMgr);              foreach (XmlNode node in VerseListNode)              {                  var thisVerse = new Verse();                  thisVerse.verseName = node.Attributes.GetNamedItem("name").Value;  //  Define nodes to loop through Song Verses                  XmlNodeList LineListNode = xmlDoc.SelectNodes("//ns:lines", nsMgr);                  foreach (XmlNode node2 in LineListNode)                  {                      var thisLyric = new Lyric();                      thisLyric.verseLyric = node2.InnerText;                      thisVerse.lyricList.Add(thisLyric);                  }                  thisSong.verseList.Add(thisVerse);              }              return thisSong;          }      }    

The 2 PROBLEMS I have run into are this:
1) Using SelectSingleNode, the result returns only the first Course found in the file as the only Course for each Student.

Sample Result:

_Les Test Song
Author Unknown
@some statement here
123456789
2
some comments or notes here

v1
   verse one lyrics
c1
   verse one lyrics
b1
   verse one lyrics
p1
   verse one lyrics
i1
   verse one lyrics
e1
   verse one lyrics
o1
   verse one lyrics

2) Alternatively, using SelectNodes, the result returns all of the Courses found in the file for every Student.

Sample Result:

_Les Test Song
Author Unknown
@some statement here
123456789
2
some comments or notes here

v1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
c1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
b1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
p1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
i1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
e1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics
o1
   verse one lyrics
   chorus one lyrics
   Bridge one lyrics
   line 2
   line 0rics
   intro one lyrics
   Ending one lyrics
   Other one lyrics

Any guidance in how to get the grouping that mirrors the sample XML song file is most appreciated. Any c-sharp technique will do. Thank you in advance for reading through this rather verbose problem presentation.

No comments:

Post a Comment