1

I am fairly new to C# and I am really struggling with this problem.

So basically I have a XML file that is like this:

<groups>
<group id="1" name="group1" location="null">
    <member id="1" name="Jack" age="32"/>
    <member id="2" name="Tom" age="25"/>
    <member id="3" name="John" age="32"/>
</group>
<group id="2" name="group2" location="null">
        <member id="1" name="Bob" age="31"/>
        <member id="2" name="Michael" age="34"/>
        <member id="3" name="Mike" age="44"/>
</group>

It's not exactly that, but it follows the exact same format. So what I want to do is, put all members of a group into it's own list. So each group would have it's own list. So Jack, Tom, John (members of group id 1) would be in list1, and Bob, Michael and Mike (members of group id 2) would be in list2. I currently have this, this basically puts all members, regardless of their group, into the same list, which is not what I want. The solution is probably very simple, I just have no experience with working with XML files so no idea how to advance with this one.

String XMLURL = "link_to_xml";

using (XmlTextReader reader = new XmlTextReader(XMLURL))
{
    while (reader.Read())
    {
        switch (reader.NodeType)
        {
            case XmlNodeType.Element:
                switch (reader.Name)
                {
                    case "member":
                        if (reader.HasAttributes)
                        {
                            reader.MoveToAttribute(1);
                            players.Add(reader.Value.ToUpper());
                        }
                        break;
                }
                break;
            }
        }
    }
}
krlzlx
  • 5,752
  • 14
  • 47
  • 55
ekl
  • 53
  • 1
  • 4
  • Is the XML file so huge that it cannot be loaded into memory? If so, a streaming algorithm like the one you have is needed. Otherwise, Linq to XML provides by far the easier solution. – dbc Oct 23 '15 at 21:48
  • Depends what counts as "huge", it has 50 to 400 entries and updates (new entries are added and old ones removed) every 20 seconds or so. I think that's pretty big? So the Linq solution only works for "static" XML files? – ekl Oct 23 '15 at 21:59
  • No, that's not huge, that's pretty small actually. Huge is 10s of megabytes or more, e.g. [What is the best way to parse (big) XML in C# Code?](http://stackoverflow.com/questions/676274/what-is-the-best-way-to-parse-big-xml-in-c-sharp-code). Linq to XML should be fine. But no matter what you use you'll need to reload after the file changes. – dbc Oct 23 '15 at 22:07
  • Alright, will try the Linq solution. Thanks. – ekl Oct 23 '15 at 22:08

2 Answers2

0

You can use System.Xml.Linq like this:

    private List<string[]> GetGroups(string xml)
    {
        List<string[]> result = new List<string[]>();
        XDocument document = XDocument.Parse(xml);
        XElement groups = document.Root;
        foreach (XElement group in groups.Elements("group"))
        {
            result.Add(group.Elements("member").Select(item => item.Attribute("name").ToString()).ToArray());
        }
        return result;
    }

I didn't run this with a test file. I just wanted to give you an idea of how to use the System.Xml.Linq namespace classes.

Lorek
  • 855
  • 5
  • 11
  • I'm getting a `Data at the root level is invalid. Line 1, position 1.` error with XDocument.Parse. The XML file I am using has .php extension (it's called "xml.php", but it's just pure XML), could that cause the error? XmlTextReader has no trouble reading it though. The XML file is not managed by me so I cannot really change that if that's causing the problem, and it's dynamic so I cannot really manually fix it either. – ekl Oct 23 '15 at 22:08
  • You might just have some weird character at the beginning of the file. What is the first character? – Lorek Oct 23 '15 at 22:11
  • @bulf - Probably there is a [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark) at the beginning of the string. How did you load the file into memory? It would be better to use [`XDocument.Load(string filename)`](https://msdn.microsoft.com/en-us/library/bb343181.aspx) since the internal stream reader should strip off the BOM. – dbc Oct 23 '15 at 22:13
  • Yeah. My example was just showing you how to use the classes once you get the data loaded. Load() will probably work for you. – Lorek Oct 23 '15 at 22:14
  • Yeah, XDocument.Load worked, I used the solution jdweng posted below. Thanks anyway! – ekl Oct 23 '15 at 22:44
0

You can do it easily with XML Linq.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Globalization;


namespace ConsoleApplication53
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml =
             "<groups>" +
                "<group id=\"1\" name=\"group1\" location=\"null\">" +
                    "<member id=\"1\" name=\"Jack\" age=\"32\"/>" +
                    "<member id=\"2\" name=\"Tom\" age=\"25\"/>" +
                    "<member id=\"3\" name=\"John\" age=\"32\"/>" +
                "</group>" +
                "<group id=\"2\" name=\"group2\" location=\"null\">" +
                        "<member id=\"1\" name=\"Bob\" age=\"31\"/>" +
                        "<member id=\"2\" name=\"Michael\" age=\"34\"/>" +
                        "<member id=\"3\" name=\"Mike\" age=\"44\"/>" +
                "</group>" +
             "</groups>";

            XDocument doc = XDocument.Parse(xml);
            var results = doc.Descendants("group").Select(x => new {
                id = (int)x.Attribute("id"),
                name = x.Attribute("name").Value,
                location = x.Attribute("location").Value,
                memebers = x.Elements("member").Select(y => new {
                    id = (int)y.Attribute("id"),
                    name = y.Attribute("name").Value,
                    age = (int)y.Attribute("age")
                }).ToList()
            }).ToList();
        }

    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20