我目前正在学习如何在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类。
是否有更有效的方法进行此解析,并获得下一个每小时的预测?
答案 0 :(得分:1)
如果您不想使用XDocument及其XElements,您可以创建一个可供XmlSerializer使用的类层次结构(DataContractSerializer无法真正利用,因为它不支持XML属性,它将回退到XmlSerializer)。
好处是它会降低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;
}
您的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元素都成为一个类,每个类都为每个子元素获取公共属性。很明显,我们需要weatherdata
,product
,time
和location
的课程。 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
这样的库