4

I am trying to parse a XML file in C# using Visual Studio and show the data in a ListBox, but I don't know how to parse it when I'm dealing with a nested XML file.

This is the code from the XML file:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root [
  <!ELEMENT root (Persons*)>
  <!ELEMENT Persons (name)>
  <!ELEMENT IsMale (#PCDATA)>
  <!ELEMENT Age (#PCDATA)>
  <!ELEMENT Name (#PCDATA)>
  <!ELEMENT LikedPerson (name)>
 ]>
<root>
  <Persons name ="Bob">
    <IsMale>true</IsMale>
    <Age>30</Age>
    <LikedPerson name ="Iulia">
      <IsMale>false</IsMale>
      <Age>32</Age>
    </LikedPerson>
  </Persons>
</root>

The code I've written in C# successfully return me only the name, gender and age for every person, but I don't know how to write to show me also the person_liked:

private void LoadPersons()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("Baza_de_cunostinte.xml");

        foreach (XmlNode node in doc.DocumentElement) 
        {
            string name = node.Attributes[0].Value;
            int age = int.Parse(node["Age"].InnerText);
            bool isMale = bool.Parse(node["IsMale"].InnerText);

//          Persons likedPerson.name = Persons.node.Attributes[0].Value ?  
//          .....

            listBox.Items.Add(new Persons(name, age, isMale, likedPerson));
        }
    }

    private void listBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (listBox.SelectedIndex != -1)
        {
            propertyGrid1.SelectedObject = listBox.SelectedItem;
        }
    }

This is the definition of the Persons.cs :

class Persons
{
    public string Name { get; private set; }
    public int Age { get; private set; }
    public bool IsMale { get; private set; }
    public Persons LikedPerson { get; private set; }

    public Persons(string name, int age, bool isMale, Persons likedPerson)
    {
        Name = name;
        Age = age;
        IsMale = isMale;
        LikedPerson = likedPerson;
    }
}
2
  • Can LikedPerson occur more than once? Commented Jan 15, 2016 at 13:55
  • 1
    You'll need to update your class for that as well then, as it stands now it could only occur once in the CLR object. Commented Jan 15, 2016 at 15:27

4 Answers 4

2
XmlSerializer mySerializer = new XmlSerializer(typeof(Persons));
// Create a FileStream or textreader to read the xml data.
FileStream myFileStream = new FileStream("xmldatafile.xml", FileMode.Open);

var person = (Persons)  mySerializer.Deserialize(myFileStream);

You also need to add constructor without parameter for Persons class.

Sign up to request clarification or add additional context in comments.

4 Comments

It gives me the following error on the XmlSerializer... line : Proiect_SBC.Persons is inaccessible due to its protection level. Only public types can be processed.
Make the class definition public: public class Persons{...}
Now I have an error on var person = (Persons) mySerializer.Deserialize(myFileStream); ->> {"There is an error in XML document (10, 2)."}
This is a decent suggestion @RadinGospodinov, but won't work without significant reworking of the class (or the XML)...
1

The most natural way to do this is to use the XmlSerializer, as suggested, but to do so you'll have to refactor your classes a bit:

[XmlType(Namespace="", TypeName="root")]
public class PersonCollection
{
    [XmlElement(Namespace="", ElementName="Persons")]
    public List<Persons> People { get; set; }
}

public class Persons
{
    [XmlAttribute(AttributeName="name")]
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsMale { get; set; }

    public Persons LikedPerson { get; set; }

    public Persons() { }

    public Persons(string name, int age, bool isMale, Persons likedPerson)
    {
        Name = name;
        Age = age;
        IsMale = isMale;
        LikedPerson = likedPerson;
    }
}

Then you can do something like this:

XmlSerializer ser = new XmlSerializer(typeof(PersonCollection));

PersonCollection pc = (PersonCollection)ser.Deserialize(File.OpenRead("Baza_de_cunostinte.xml"));
foreach (Persons p in pc.People)
{
   // you now have a fully populated object
}

and the pc.People list will contain your Persons objects.

Comments

1

@user3063909,

1- Use a XSD for the XML definition. Ex:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Persons" maxOccurs="unbounded" minOccurs="0" type="Persons"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="Persons">
    <xs:sequence>
      <xs:element type="xs:string" name="IsMale"/>
      <xs:element type="xs:int" name="Age"/>
      <xs:element name="LikedPerson" type="Persons"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="name" />
  </xs:complexType>
</xs:schema>   

2- The Persons class should look like this:

namespace StackOverflow
{
    public class Root
    {
        [XmlElement("Persons")]
        public List<Persons> Persons { get; set; }
    }

    public class Persons
    {
        public string IsMale { get; set; }
        public int Age { get; set; }
        public Persons LikedPerson { get; set; }

        [XmlAttribute("Name")]
        public string Name { get; set; }
    }
}

3- The serializer class:

namespace StackOverflow
{
    public class XmlSerializerHelper<T> where T : class 
    {
        private readonly XmlSerializer _serializer;

        public XmlSerializerHelper()
        {
            _serializer = new XmlSerializer(typeof(T));
        }

        public T BytesToObject(byte[] bytes)
        {
            using (var memoryStream = new MemoryStream(bytes))
            {
                using (var reader = new XmlTextReader(memoryStream))
                {
                    return (T)_serializer.Deserialize(reader);
                }
            }
        }
    }
}

4- And finally, call it this way:

var fileBytes = File.ReadAllBytes("C:/xml.xml");
var persons = new XmlSerializerHelper<Root>().BytesToObject(fileBytes);

The result, will be the root class with a list of persons.

Cheers.

Comments

0

You can get LikedPerson node and get it's name/age like you do now. In order to avoid code duplication, you can create a method, which takes XmlNode, parses it recursively and returns a Person. But the better way is to use XmlSerializer

foreach (XmlNode node in doc.DocumentElement) 
{
    string name = node.Attributes[0].Value;
    int age = int.Parse(node["Age"].InnerText);
    bool isMale = bool.Parse(node["IsMale"].InnerText);

    var likedPerson = node.SelectSingleNode("LikedPerson");

    if (likedPerson != null){
        string name = likedPerson.Attributes[0].Value;
        //age, gender, etc.        
    }        
}

3 Comments

I've also added "private System.Xml.XmlNode likedPerson;" in Persons.cs, and now it shows me "LikedPerson" in the PropertyGrid, but it doesn't show anything next to it... (like name, age...)
Can you update your code, please? I'll try to reproduce your problem.
I can't comment another answer: There is an error in XML document (10, 2). Your root element is not "Persons", that's why you get this error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.