简体   繁体   中英

Load and get values from XML

First of all, I'm new to C# but have some experience with other programming language. I have now encountered a problem which I don't know how to tackle so I haven´t done anything so far, because I really don´t know how to begin this.

I have an XML file containing data that I would like to match and store it´s child elements into variables.

Ex: I would like to retrieve week with the weeknumber value of "2" from the xml and store all child elements to separate variables in my application.

  • int groupNumber = 1;
  • string starTtime = 10:00;
  • int startDay = 8;
  • string endTime = 12:00;
  • int endDay = 8;
<root>
    <weeks>
        <week weeknumber="1">
            <groupNumber>1</groupNumber>
            <starttime>10:00</starttime>
            <startday>8</startday>
            <endtime>12:00</endtime>
            <endday>8</endday>
        </week> 
        <week weeknumber="2">
            <groupNumber>1</groupNumber>
            <starttime>10:00</starttime>
            <startday>8</startday>
            <endtime>12:00</endtime>
            <endday>8</endday>
        </week> 
        <week weeknumber="3">
            <groupNumber>1</groupNumber>
            <starttime>10:00</starttime>
            <startday>8</startday>
            <endtime>12:00</endtime>
            <endday>8</endday>
        </week> 
    </weeks>
</root> 

LINQ to XML API exists in the.Net Framework since 2007.

c#

void Main()
{
    const string fileName = @"e:\Temp\Weeks.xml";
    XDocument xdoc = XDocument.Load(fileName);
    
    var xelem = xdoc.Descendants("week")
        .Where(x => x.Attribute("weeknumber").Value.Equals("2"))
        .FirstOrDefault();

    int groupNumber = Convert.ToInt32(xelem.Element("groupNumber").Value);
    string starTtime = xelem.Element("starttime").Value;
    int startDay = Convert.ToInt32(xelem.Element("startday").Value);
    string endTime = xelem.Element("endtime").Value;
    int endDay = Convert.ToInt32(xelem.Element("endday").Value);
    
    Console.WriteLine("groupNumber={0}, starTtime={1}, startDay={2}, endTime={3}, endDay={4}", groupNumber
        , starTtime
        , startDay
        , endTime
        , endDay);
}

Use Xml Serialization and then parse results

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

namespace ConsoleApplication
{
    public class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        public static void Main(string[] args)
        {
            XmlReader reader = XmlReader.Create(FILENAME);
            XmlSerializer serializer = new XmlSerializer(typeof(Root));
            Root root = (Root)serializer.Deserialize(reader);
        }
    }
    [XmlRoot("root")]
    public class Root
    {
        [XmlArray("weeks")]
        [XmlArrayItem("week")]
        public List<Week> week { get; set; }
    }
    public class Week
    {
        public int groupNumber { get; set; }

        private DateTime _starttime { get; set; }
        public string starttime 
        {
            get {return _starttime.ToString("HH:mm");}
            set { _starttime = DateTime.ParseExact(value, "HH:mm", System.Globalization.CultureInfo.InvariantCulture); } 
        }
        public int startday { get; set; }
        private DateTime _endtime { get; set; }
        public string endtime
        {
            get { return _endtime.ToString("HH:mm"); }
            set { _endtime = DateTime.ParseExact(value, "HH:mm", System.Globalization.CultureInfo.InvariantCulture); }
        }
        public int endday { get; set; }
    }

}

Here's a lazy but effective approach. This isn't a destination but a starting point.

First, paste your XML into the Xml2Csharp website. It will generate some classes that correspond to your XML. It's not always perfect but once it's done you have something instead of nothing, and it's easier to tweak something. It gave me this:

using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace Xml2CSharp
{
    [XmlRoot(ElementName="week")]
    public class Week {
        [XmlElement(ElementName="groupNumber")]
        public string GroupNumber { get; set; }
        [XmlElement(ElementName="starttime")]
        public string Starttime { get; set; }
        [XmlElement(ElementName="startday")]
        public string Startday { get; set; }
        [XmlElement(ElementName="endtime")]
        public string Endtime { get; set; }
        [XmlElement(ElementName="endday")]
        public string Endday { get; set; }
        [XmlAttribute(AttributeName="weeknumber")]
        public string Weeknumber { get; set; }
    }

    [XmlRoot(ElementName="weeks")]
    public class Weeks {
        [XmlElement(ElementName="week")]
        public List<Week> Week { get; set; }
    }

    [XmlRoot(ElementName="root")]
    public class Root {
        [XmlElement(ElementName="weeks")]
        public Weeks Weeks { get; set; }
    }
}

I put the XML in a file in a unit test project along with the classes and created this simple unit test. It's not checking everything, but it verifies that the XML gets deserialized into an instance of the class and that it contains three "Weeks" just like in the file.

using System.IO;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Xml2CSharp; // the website created this namespace. You don't need to keep it.

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void Deserializes_Xml()
        {
            using var stream = File.OpenText("xmlfile1.xml");
            var serializer = new XmlSerializer(typeof(Root));
            var deserialized = (Root) serializer.Deserialize(stream);
            Assert.AreEqual(3, deserialized.Weeks.Week.Count);
        }
    }
}

That test passes. We're over the hump. Now, given an XML file it's easy to read it into some objects that contain the data you need.

Some other steps might be to rename the classes (if you care) or to change some of the types. For example a lot of these values are apparently integers but the auto-generated code doesn't know that so it uses strings. You could change a property like this:

public int GroupNumber { get; set; }

...and now instead of the string "1" you have the integer 1.

Now, given a deserialized instance of the class it's easy to read and manipulate. If you want weeks where the week number is two you can write

IEnumerable<Week> weeksWithWeekNumberTwo = 
    deserialized.Weeks.Week.Where(w => w.Weeknumber == 2);

You could create more variables but you probably don't need to. You can do this:

foreach (var week in weeksWithWeekNumberTwo)
{
    // each time this loop executes the "week" variable already
    // has all the properties. You could assign them to more
    // variables but one variable ("week") with properties is easier.
}

A benefit of the approach is that it keeps different parts of the work separate. First you know that you're reading the XML correctly. If you're not sure you could add more to the test. Once you know that you can move on and forget about reading XML. You're just working with objects and properties.

Now you can focus on logic that looks at instances of those classes and reads their properties. If you wanted to write tests for that you could just create instances of the class. You wouldn't need to create XML files for different scenarios.

Everything is harder if you mix those steps together, perhaps looking for certain values while you read the file and while you parse the XML. If you don't get the result you expect, which part was wrong? It's harder to tell if we're doing multiple things at once.

And because we're lazy and started with a tool to create the classes for us we're less likely to make it even more confusing by wondering if we used the correct attributes in the correct places. All of that uncertainty can easily add up to an hour or several. This took several minutes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM