解析XML和创建对象的更好方法是什么?

时间:2016-10-13 15:31:17

标签: c# .net xml xml-parsing

我目前正在学习如何在C#中处理XML,我正在尝试从这个URL解析并创建一个对象:https://api.met.no/weatherapi/locationforecast/1.9/?lat=60.10&lon=9.58

函数调用

var data = GetXmlData(url);
LocationForecast forecastNextHour = data.First();

解析

private IEnumerable<LocationForecast> GetXmlData(string url)
{
    // Create new XDocument
    XDocument xdoc = XDocument.Load(url);

    // Get first element in XML with forecast datatype (next hour)
    XElement selectedElement = xdoc.Descendants().First(x => (string)x.Attribute("datatype") == "forecast");

    var locationData = from x in selectedElement.Descendants()
                   let temperature = x.Element("temperature")
                   where temperature != null
                   let windDirection = x.Element("windDirection")
                   where windDirection != null
                   let windSpeed = x.Element("windSpeed")
                   where windSpeed != null
                   let windGust = x.Element("windGust")
                   where windGust != null
                   let humidity = x.Element("humidity")
                   where humidity != null
                   let pressure = x.Element("pressure")
                   where pressure != null
                   let cloudiness = x.Element("cloudiness")
                   where cloudiness != null
                   let fog = x.Element("fog")
                   where fog != null
                   let lowClouds = x.Element("lowClouds")
                   where lowClouds != null
                   let mediumClouds = x.Element("mediumClouds")
                   where mediumClouds != null
                   let highClouds = x.Element("highClouds")
                   where highClouds != null
                   let dewpointTemperature = x.Element("dewpointTemperature")
                   where dewpointTemperature != null

    // Get data from selected portion of XML
    select new LocationForecast()
    {
        Temperature = (double)temperature.Attribute("value"),
        WindDirection = (double)windDirection.Attribute("deg"),
        WindSpeed = (double)windSpeed.Attribute("mps"),
        WindGust = (double)windGust.Attribute("mps"),
        Humidity = (double)humidity.Attribute("value"),
        Pressure = (double)pressure.Attribute("value"),
        Cloudiness = (double)cloudiness.Attribute("percent"),
        Fog = (double)fog.Attribute("percent"),
        LowClouds = (double)lowClouds.Attribute("percent"),
        MediumClouds = (double)mediumClouds.Attribute("percent"),
        HighClouds = (double)highClouds.Attribute("percent"),
        DewpointTemperature = (double)dewpointTemperature.Attribute("value"),
    };

    return locationData;
}

代码有效但看起来效率有点低,因为我返回一个IEnumerable,我必须在返回的对象上使用.First()来获取具有属性的实际对象。使用此代码,我也无法掌握其他时间元素,其中from =“”和to =“”属性相同(预测该小时),我想得到每小时的预测至少下三个小时。

我尝试过使用xsd.exe从架构中创建一个类,但这没有任何意义,所以目前我坚持使用我的LocationForecast类。

是否有更有效的方法进行此解析,并获得下一个每小时的预测?

1 个答案:

答案 0 :(得分:1)

如果您不想使用XDocument及其XElements,您可以创建一个可供XmlSerializer使用的类层次结构(DataContractSerializer无法真正利用,因为它不支持XML属性,它将回退到XmlSerializer)。

好处是它会降低GetXmlData方法的复杂性。作为一个缺点,你没有得到更少的代码,你似乎希望值是双倍需要一些操作员魔术来支持隐式转换。

GetXmlData

此方法现在将打开一个流并使用XmlSerializer获取所有天气数据。获得后,它将获得预测集合中的第一个Location项。如果您需要其他项目,可以更改此项。

public Location GetXmlData(string url)
{
    Location loc;
    using(var wc = new WebClient())
    {
        // Type you want to deserialize
        var ser = new XmlSerializer(typeof(WeatherData));
        using(var stream = wc.OpenRead(url))
        {   
            // create the object, cast the result
            var w = (WeatherData) ser.Deserialize(stream);
            // w.Dump(); // linqpad testing
            // what do we need
            loc =  w.Product.ForeCasts.First().Location;
        }
    }
    return loc;
}

(De)序列化类

您的XML看起来大致如下:

<weatherdata>
    <product>
        <time from="2016-10-13T20:00:00Z" to="2016-10-13T20:00:00Z">
            <location altitude="485" latitude="60.1000" longitude="9.5800">
                <temperature id="TTT" unit="celsius" value="-0.9"/>
            </location>
        </time>
        <time from="2016-10-13T20:00:00Z" to="2016-10-13T20:00:00Z">
            <location altitude="485" latitude="60.1000" longitude="9.5800">
                <temperature id="TTT" unit="celsius" value="-0.9"/>
                <fog id="FOG" percent="-0.0"/>
            </location>
        </time>
        <!-- many more -->
    </product>       
 </weatherdata>

每个XML元素都成为一个类,每个类都为每个子元素获取公共属性。很明显,我们需要weatherdataproducttimelocation的课程。 location里面的元素看起来都很相似所以我在那里试了一下捷径。首先是明确的课程。请注意Attributes以引导正确的反序列化行为。

// the root
[XmlRoot("weatherdata")]
public class WeatherData
{
    [XmlElement("product")] 
    public Product Product {get; set;}
}

public class Product
{
    [XmlElement("time")] 
    public List<Time> ForeCasts {get;set;}
}

public class Time
{
   [XmlAttribute("from")]
   public DateTime From {get;set;}
   [XmlAttribute("to")]
   public DateTime To {get;set;}
   [XmlElement("location")]
   public Location Location{get;set;}
}


public class Location 
{
   [XmlAttribute("altitude")]
   public string Altitude {get;set;}
   [XmlElement("temperature")]
   public Value Temperature {get;set;}
   [XmlElement("windDirection")]
   public Measurement WindDirection {get;set;} 
   [XmlElement("pressure")]
   public Measurement Pressure {get;set;}
   [XmlElement("fog")]
   public Percent Fog {get;set;}
   // add the rest
}

特殊情况

位置内的元素都具有相似的属性,因此我创建了一个主类来捕获所有变体:

// covers all kind of things
public class Measurement
{
    [XmlAttribute("id")]
    public string Id {get;set;}
    [XmlAttribute("unit")]
    public string Unit {get;set;}
    [XmlAttribute("deg")]
    public double Deg {get;set;}
    [XmlAttribute("value")]
    public double Value {get;set;}
    [XmlAttribute("mps")]
    public double Mps {get;set;}
    [XmlAttribute("percent")]
    public double Percent {get;set;}
}

具体测量

您的用例似乎表明您希望使用测量的某个属性(现在属性)中的双精度数。为此目的,我将子量化和价值分析。这里的类实现了implicit conversion operator,因此不需要为此类的使用者进行强制转换。

// for properties that use the Value attribute
public class Value:Measurement
{
    // operators for easy casting
    public static implicit operator Value(Double d) 
    {
        var v = new Value();
        v.Value = d;
        return v;
    }
    public static implicit operator Double(Value v)
    {
        return v.Value;
    }

    public override string ToString() 
    {
       return this.Value.ToString();
    }
}

// for properties that use the Percent attribute
public class Percent:Measurement
{
    // operators for easy casting
    public static implicit operator Percent(Double d) 
    {
        var p = new Percent();
        p.Percent = d;
        return p;
    }
    public static implicit operator Double(Percent p)
    {
        return p.Percent;
    }

    public override string ToString() 
    {
       return this.Percent.ToString();
    }
}

一个小的测试方法如下:

void Main()
{
    var url = @"https://api.met.no/weatherapi/locationforecast/1.9/?lat=60.10&lon=9.58";
    var loc = GetXmlData(url);
    Double f = loc.Fog;
    f.Dump("Percent as double");
    Double t = loc.Temperature;
    t.Dump("Temperature as double");
}

如果您不希望属性不是double类型,则可以重新使用LocationForecast类,但在这种情况下,您需要实例化并映射自己的值,像:

 var lf = new LocationForcast {
     Temperature = loc.Temperature,
     Precent = loc.Percent,
     // etc.
 };

或使用像Automapper

这样的库