The Woo
The Woo

Reputation: 18675

XML Element Selection

I am collecting XML data from a weather website xml file here.

I have created the following code, which finds the first instance of 'temp_c' and returns the value. This is the temperature at the current moment.

using (XmlReader reader = XmlReader.Create(new StringReader(weather)))
   {
      while (reader.Read())
      {
         switch (reader.NodeType)
            {
               case XmlNodeType.Element:
                 if (reader.Name.Equals("temp_c"))
                 {
                    reader.Read();
                    temp_c = reader.Value;
                 }
                 break;
            }
     }
}
return temp_c

This returns the value of the first instance in the XML file called "temp_c" to the string called "temp_c"

What I would now like to do is use the Element in the XML document called "period" and the element found with period called "fcttext". When "period = 0" it means "today", 1 = tomorrow, etc; and I am after the "fcttext_metric" data that is associated with that period value.

I've been struggling to work the examples and XML reading codes I've found into the current code that I have here. Can anyone please point me in the right direction?

Edit

Here is an example of the XML file:

<forecast>
   <forecastday>
      <period>0</period>
      <fcttext_metric>Sunny</fcttext_metric>
   <forecastday>
      <period>1</period>
      <fcttext_metric>Cloudy</fcttext_metric>

Upvotes: 1

Views: 197

Answers (1)

Todd Bowles
Todd Bowles

Reputation: 1574

This ended up being more annoying that I originally expected, but you can create an object graph from the XML using a DataContractSerializer and a set of classes that match the XML you are reading.

First, you create your classes, with appropriate DataContract attributes.

[DataContract(Name = "response", Namespace = "")]
public class WeatherData
{
    [DataMember(Name = "forecast")]
    public Forecast Forecast { get; set; }
}

[DataContract(Name = "forecast", Namespace = "")]
public class Forecast
{
    [DataMember(Name = "txt_forecast")]
    public TxtForecast TxtForecast { get; set; }
}

[DataContract(Name = "txt_forecast", Namespace = "")]
public class TxtForecast
{
    [DataMember(Name = "forecastdays")]
    public ForecastDay[] ForecastDays { get; set; }
}

[DataContract(Name = "forecastday", Namespace = "")]
public class ForecastDay
{
    [DataMember(Name = "period", Order = 1)]
    public int Period { get; set; }

    public string FctText { get; set; }

    [DataMember(Name = "fcttext", EmitDefaultValue = false, Order = 5)]
    private CDataWrapper FctTextWrapper
    {
        get { return FctText; }
        set { FctText = value; }
    }
}

Heres where it got complicated. The fcttext element is CDATA, which the DataContractSerializer doesn't support by default.

Using the wonderful answer available at Using CDATA with WCF REST starter kits, you create a CDataWrapper class. I won't repeat the code here (because that would be pointless), just follow the link.

You can see that I've already used the CDataWrapper class above in order to work with the fcttext element.

Once you've got the class structure setup you can use the following code to extract the information you were after. You're just navigating the object graph at this point, so you can use whatever C# you want (I've used a simple LINQ query to find Period 0 and print out the fcttext for it).

var s = new DataContractSerializer(typeof(WeatherData));
var reader = XmlReader.Create("http://api.wunderground.com/api/74e1025b64f874f6/forecast/conditions/q/zmw:00000.1.94787.xml");
var o = (WeatherData)s.ReadObject(reader);

var firstPeriod = o.Forecast.TxtForecast.ForecastDays.Where(a => a.Period == 0).Single();
Console.WriteLine(firstPeriod.FctText);

You can extend the classes as necessary to give you access to additional fields inside the XML. As long as the DataMember names match the XML fields it will all work.

Here's a short summary of some problems I ran into for anyone interested:

  • I had to set the Namespace attributes of all of the classes to the empty string, because the XML doesn't have any namespace info in it.
  • The Order attributes were important on the ForecastDay class. If they are omitted, the DataContractSerializer ends up not reading the fcttext field in at all (because it thinks fcttext should come first? not sure why to be honest).

Upvotes: 1

Related Questions