Pallas
Pallas

Reputation: 1579

Get child elements of xml based on parent element's value

I have an XML string that looks like the following:

<?xml version="1.0" encoding="utf-8"?>
<Report>
  <Version Name="2.0">
    <Note>The meaning of life is 42.</Note>
    <Note>Hitchiker's Guide to the Galaxy</Note>
  </Version>
  <Version Name="2.1">
    <Note>Football is awesome!</Note>
    <Note>Let's go sailing!</Note>
  </Version>
</Report>

I'm trying to output the information for each version to my webpage. So far I'm able to get the version number but haven't been able to associate the note with the version number. Here's what I have:

var xml = Client.GetReleaseNotes();

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

var versions = Client.GetUniqueNames(doc.SelectNodes("//Version"));
versions.Reverse();

Where Client.GetReleaseNotes(); gives me my XML string to parse.

public List<string> GetUniqueNames(XmlNodeList nodes)
        {
            List<string> list = new List<string>();
            foreach (XmlNode node in nodes)
            {
                var attr = node.Attributes["Name"] ?? node.Attributes["Title"];
                string name = attr.InnerText;
                if (!list.Contains(name) && name.IndexOf("sample", StringComparison.OrdinalIgnoreCase) == -1)
                    list.Add(name);
            }

            list.Sort();

            return list;
        }

public List<string> GetVersionNotes(XmlNodeList nodes)
        {
            List<string> list = new List<string>();

            foreach (XmlNode node in nodes)
            {
                list.Add(node.Value);
            }

            list.Sort();

            return list;
        }

As you can see below I used a foreach loop to output the version number to the screen.

<section id="release-notes">
    <div class="container">
        <div class="row">
            <div class="col-lg-12 text-center">
                <h2 class="section-heading">Release Notes</h2>
            </div>
        </div>
        <div class="row">
            @foreach (string version in versions)
            {
                <div class="col-md-4">
                    <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                        <div class="panel panel-default">
                            <div class="panel-heading" role="tab" id="webHeading">
                                <h4 class="panel-title">
                                    <a role="button" data-toggle="collapse" data-parent="#accordion" href="#webPanel" aria-expanded="true" aria-controls="webPanel">
                                        Version @version
                                    </a>
                                </h4>
                            </div>
                            <div id="webPanel" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="webHeading">
                                <ul class="list-group">
                                    @{
                                        var notes = Client.GetVersionNotes(doc.SelectNodes("//version[@value='" + version + "']/note"));
                                    }

                                    @foreach (string note in notes)
                                    {
                                        <li class="list-group-item">@note</li>
                                    }
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            }
        </div>
    </div>
</section>

I then proceeded to use another foreach loop and try and grab the child elements for each version. This way I could have a way to associate the notes with it's version. I read that you could use the SelectNodes method to get child nodes based on a specific criteria like the value of a parent node. That's what prompted me to create the line var notes = Client.GetVersionNotes(doc.SelectNodes("//version[@value='" + version + "']/note"));

However I'm still unable to get the notes. What am I doing wrong? Can someone please point me in the right direction?

Upvotes: 1

Views: 2397

Answers (2)

Enigmativity
Enigmativity

Reputation: 117009

Another option you have is to use LINQ to XML.

Here's how:

var doc = XDocument.Parse(@"<?xml version=""1.0"" encoding=""utf - 8""?>
<Report>
  <Version Name = ""2.0"">
    <Note>The meaning of life is 42.</Note>
    <Note>Hitchiker's Guide to the Galaxy</Note>
  </Version>
  <Version Name = ""2.1"">
    <Note>Football is awesome!</Note>
    <Note>Let's go sailing!</Note>
  </Version>
</Report>");

var lookup =
    doc
        .Descendants("Note")
        .ToLookup(
            x => x.Parent.Attribute("Name").Value,
            x => x.Value);

That gives you this lookup:

lookup

Then you can do this:

var notes = lookup["2.1"].ToList();

And that gives you:

result

Upvotes: 1

har07
har07

Reputation: 89285

Notice that element and attribute names in XPath are case-sensitive. Also, you have the wrong attribute name in your XPath (should've been @Name instead of @value). Try this way to get the corresponding Note elements :

doc.SelectNodes("//Version[@Name='" + version + "']/Note")

Upvotes: 2

Related Questions