Select value in Xelement within Xelement? - c#

For a Webradio app in windows Phone, i'm trying to read an XML file with the data, but i'm having a problem with a specific field. The XML File looks like this:
<brands xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<brandgroup>
<brand>
<code>blabla</code>
<name>blabla</name>
<logo>blabla</logo>
<websiteurl>blabla</websiteurl>
<audiostreams>
<audiostream streamurl="www.1.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.2.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.3.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
<brand>
</brand>
</brandgroup>
other 'brandgroups' with other 'brand'
</brand>
With the next Code i'm capable of getting the Name, Code, and Website into and object of Class Station for every brand inside every brandgroup.
XDocument loadedData = XDocument.Load("netten.xml");
var data = from query in loadedData.Descendants("brand")
select new Station
{
Name = (string)query.Element("name"),
Code = (int)query.Element("code"),
Website = (string)query.Element("websiteurl"),
};
However, I can't find a way to get the audiostream. There is the 'audiostreams' element wich has 3 child 'audiostream' elements, where i need the 'streamurl'.
Best would be to store the 3 streamurls so i could change the quality later on. Then i should need to have a field in Class Station:
String[] streamurls = {www.1.mp3, www.2.mp3, www.3.mp3};
and store the 3 streamurls in there to select later on. I've tried some things that are posted here related to XML, Attribute and XElement, but i can't get it to work.
Is there anyone out there that knows a way?
Btw I don't really get how to highlight code and stuff here, I hope it works, otherwse I'm really sorry...

If you really want just the URLs, you can do something like (assuming the is a property of type IEnumerable<string> (or string[]) called StreamUrls on Station):
from brand in loadedData.Descendants("brand")
select new Station
{
Name = (string)brand.Element("name"),
Code = (int)brand.Element("code"),
Website = (string)brand.Element("websiteurl"),
StreamUrls = brand.Element("audiostreams")
.Elements("audiostream")
.Attributes("streamurl")
.Select(a => a.Value)
.ToArray()
}
If you want to get other information from the audiostream elements, declare a class like:
class Stream
{
public string Url { get; set; }
public string Codec { get; set; }
public string Rate { get; set; }
public bool IsDefault { get; set; }
}
And the query would then look like:
from brand in loadedData.Descendants("brand")
select new Station
{
Name = (string)brand.Element("name"),
Code = (int)brand.Element("code"),
Website = (string)brand.Element("websiteurl"),
Streams =
(from stream in brand.Element("audiostreams").Elements("audiostream")
select new Stream
{
Url = (string)stream.Attribute("streamurl"),
Codec = (string)stream.Attribute("codec"),
Rate = (string)stream.Attribute("streamrate"),
IsDefault = (string)stream.Attribute("default") == "true"
}).ToArray()
}
(If Codec and Rate can only have certain values, expressing them as an enum would be better than string.)

Hope it is useful
static void Main(string[] args)
{
XDocument loadedData = XDocument.Load("netten.xml");
var data = from query in loadedData.Descendants("brand")
group query by new { A = query.Element("name"), B = query.Element("code"), C = query.Element("websiteurl"), D = query.Element("audiostreams") } into g
select new
{
Name = g.Key.A + "",
Code = g.Key.B + "",
Website = g.Key.C + "",
AudioStreams = g.Key.D.Elements("audiostream")
.Attributes("streamurl")
.Select(x => x.Value)
.ToArray()
};
foreach (var x in data)
{
Console.WriteLine(x.Name);
Console.WriteLine(x.Code);
Console.WriteLine(x.Website);
foreach (var url in x.AudioStreams)
Console.WriteLine(url);
}
Console.ReadKey();
}
}
The xml file:
<?xml version="1.0" encoding="utf-8" ?>
<brands xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<brandgroup>
<brand>
<code>blabla</code>
<name>blabla</name>
<logo>blabla</logo>
<websiteurl>blabla</websiteurl>
<audiostreams>
<audiostream streamurl="www.1.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.2.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.3.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
</brandgroup>
<brandgroup>
<brand>
<code>blabla2</code>
<name>blabla2</name>
<logo>blabla</logo>
<websiteurl>blabla2</websiteurl>
<audiostreams>
<audiostream streamurl="www.4.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.5.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.6.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
</brandgroup>
</brands>

Related

How can I use Linq to build a c# object from xml where an element has zero or more elements of the same type?

I'm trying to parse an xml file that looks roughly like this:
<?xml version="1.0" encoding="utf-16" ?>
<log>
<request id="1" result="Deny">
<user name="corp\joe"/>
<session number="1"/>
<file name="xyz"/>
<policy type="default" name="Default Rules" result="Deny"/>
<policy type="adgroup" name="Domain Users" result="Allow"/>
</request>
<request id="1" result="Deny">
<user name="corp\joe"/>
<session number="1"/>
<file name="abc"/>
<policy type="default" name="Default Rules" result="Deny"/>
<policy type="device" name="laptop12" result="Deny"/>
</request>
</log>
Note the presence of multiple policy elements per request.
Here's my code so far:
public class Request
{
public int Request_id { get; set; }
public string User_name { get; set; }
public string Session_number { get; set; }
public string File_name { get; set; }
}
void Main()
{
var xml = XDocument.Load(#"c:\temp\test.xml");
var query = from c in xml.Descendants("request")
where (String)c.Attribute("result") == "Deny"
select new Request() {
Request_id = (int) c.Attribute("id"),
User_name = (string) c.Element("user").Attribute("name"),
Session_number = (string) c.Element("session").Attribute("number"),
File_name = (string) c.Element("file").Attribute("name"),
};
foreach (Request r in query.ToList()) {
System.Diagnostics.Debug.WriteLine(r.User_name + "," + r.File_name);
}
}
I'm not sure how to query and capture the 0+ policy elements. Should I:
define a new class Policy {Type, Name, Result}
add a List member to the Request class
somehow populate this list when creating the new Request object as part of the Linq query
It's the somehow bit I'm stuck on. Do I need to add another Linq query within the existing Select, and if so, what would that look like? c.Descendents("policy")...
You're on the very good track! Your points are correct. You need a Policy class and a List<Policy> property in Request class. With that you need a subquery in your existing query to populate them:
var query = from c in xml.Descendants("request")
where (String)c.Attribute("result") == "Deny"
select new Request() {
Request_id = (int) c.Attribute("id"),
User_name = (string) c.Element("user").Attribute("name"),
Session_number = (string) c.Element("session").Attribute("number"),
File_name = (string) c.Element("file").Attribute("name"),
Policies = (from p in c.Elements("policy")
select new Policy() {
Type = (string) p.Attribute("type")
// (...)
}).ToList()
};

Querying XML file

i should to parse an XML file in my C# application.
This is a sample of my XML file
<?xml version="1.0" encoding="utf-8" ?>
<tokens>
<token id="1" >
<id>1</id>
<nome>"Pippo"</nome>
<numeroSillabe>"2"</numeroSillabe>
<sillabaIniziale>"Pip"</sillabaIniziale>
</token>
<token id="2">
<id>2</id>
<nome>Pluto</nome>
<numeroSillabe>2</numeroSillabe>
<sillabaIniziale>Plu</sillabaIniziale>
</token>
<token id="3">
<id>3</id>
<nome>Palla</nome>
<numeroSillabe>2</numeroSillabe>
<sillabaIniziale>Pa</sillabaIniziale>
</token>
</tokens>
For reading this file i use this code
XmlDocument document = new XMLDocument()
document.Load("Content\\Token.xml");
// arrivo al nodo
//XmlNode node = document.DocumentElement.SelectSingleNode("/tokens/token");
//dichiaro la mappa per i token
Dictionary<string, Token> mappa = new Dictionary<string, Token>();
foreach (XmlNode node in document.DocumentElement.ChildNodes)
{
//creo l'oggetto Token e lo popolo con i risultati del parse
Token token = new Token();
//compilo il campo ID
token.setId((node["id"].InnerText).Replace("\"", ""));
//compilo il nome
token.setNome((node["nome"].InnerText).Replace("\"", ""));
//compilo l numero sillabe
token.setNumeroSillabe((node["numeroSillabe"].InnerText).Replace("\"", ""));
//compilo la sillaba iniziale
token.setSillabaIniziale(node["sillabaIniziale"].InnerText);
//aggiungo all array il token
mappa.Add(token.getId(), token);
}
It works.
Now i would like to search the element from XML document for example SELECT * FROM DOCUMENT_XML WHERE numeroSilabe=2.
How can I perform this task?
If you need to navigate through your XML file, like in your code sample, you could go with something like this:
var mappa = document.SelectNodes("/tokens/token[numeroSillabe=2]") // Filter
.OfType<XmlElement>()
.ToDictionary(
t => t.GetAttribute("id"),
t => new
{
Id = t.GetAttribute("id"),
Nome = t.SelectSingleNode("nome" ).InnerText,
NumeroSillabe = t.SelectSingleNode("numeroSillabe" ).InnerText,
SillabaIniziale = t.SelectSingleNode("sillabaIniziale").InnerText
});
List<Token> tokens = mappa.Select(x=>x.Values.Where(y=>y.getNumeroSillabe()==2)).ToList();
I suppose that your token class has a method getNumeroSillabe() like getId(), which returns the value of NumeroSillabe.
By the way, I would suggest you to use automatic properties instead of defining foreach variable of your class a getter and a setter. Certainly, that would be suitable, if you don't have any logic in this methods instead of setting and getting the value of a variable, without checking anything or doing any calculation etc.
In your case:
// I don't know, which is the access modifier your class and I suppose that is public.
public class Token
{
public string Id { get; set;}
public string Nome { get; set;}
public string NumeroSillabe { get; set;}
public string SillabeIniziale { get; set;}
// here goes the methods of your class, if there are any
}
making this change, the code for the above linq query changes to the following:
List<Token> tokens = mappa.Select(x=>x.Values.Where(y=>y.NumeroSillabe==2)).ToList();
Also your code changes for instance as follows:
token.Nome = node["nome"].InnerText.Replace("\"", "");
The same holds for the other three assignments.
For further reading regrading the auto implemented properties, please look in the following link:
http://msdn.microsoft.com/en-us/library/vstudio/bb384054.aspx
use this:
var mappa = document.SelectNodes("//token[numeroSillabe=2]");
Try below code to get filtered nodes without any loop:
var document = new XmlDocument();
document.Load("Content\\Token.xml");
var filteredNodes = document.SelectNodes("tokens/token/numeroSillabe=2");

Parsing specific part of XML file

I have a .gpx XML file with the following sample:
<trk>
<name>Test</name>
<trkseg>
<trkpt lon="-84.89032996818423" lat="32.75810896418989">
<ele>225.0</ele>
<time>2011-04-02T11:57:48.000Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:cad>0</gpxtpx:cad>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
</trkseg>
</trk>
I'm using Linq to XML to parse this but I'm having a difficult time parsing the extensions section. Here's the code I'm using:
var gpxDoc = LoadFromStream(document);
var gpx = GetGpxNameSpace();
var gpxtpx = XNamespace.Get("gpxtpx");
var tracks = from track in gpxDoc.Descendants(gpx + "trk")
select new
{
Name = DefaultStringValue(track, gpx, "name"),
Description = DefaultStringValue(track, gpx, "desc"),
Segments = (from trkSegment in track.Descendants(gpx + "trkseg")
select new
{
TrackSegment = trkSegment,
Points = (from trackpoint in trkSegment.Descendants(gpx + "trkpt")
select new
{
Lat = Double(trackpoint.Attribute("lat").Value),
Lng = Double(trackpoint.Attribute("lon").Value),
Ele = DefaultDoubleValue(trackpoint, gpx, "ele"),
Time = DefaultDateTimeValue(trackpoint, gpx, "time"),
Extensions = (
from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault()
})
})
};
Here's the relevant helper code:
private static double? DefaultIntValue(XContainer element, XNamespace ns, string elementName)
{
var xElement = element.Element(ns + elementName);
return xElement != null ? Convert.ToInt32(xElement.Value) : (int?)null;
}
private XNamespace GetGpxNameSpace()
{
var gpx = XNamespace.Get("http://www.topografix.com/GPX/1/1");
return gpx;
}
The actual error I'm getting is
The following error occurred: Object reference not set to an instance of an object.
and it bombs on this code:
Extensions = (from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault();
I just don't know how to fix it.
Since you never declare the namespace (xmlns:gpxtpx="http://www.topografix.com/GPX/1/1") it is never going to match. The xml fragment you provided is not well formed due to the lack of the namespace.
If the fragment posted is snipped from a larger document, consider switching to XML API's rather than string manipulation. If that is the entirety of the XML you receive from an outside system, add it to a root node which you can declare the schema in:
<root xmlns:gpxtpx="http://www.topografix.com/GPX/1/1">
<!-- put your xml fragment here -->
</root>

Combine two LINQ queries from different sources into single object

I am gathering data from two different sources with the intention of populating a single object. My class is
public class SampleClient
{
public string ClientId { get; set; }
public string JobName { get; set; }
public string JobUrl { get; set; }
public string Version { get; set; }
}
The first LINQ query populates a collection of SampleClient objects obtaining values for all except Version. I then need to loop through the collection of SampleClient and utilize ClientId value in the second query. The result of the second query is the Version value. Here is my first LINQ query:
private void btnProjects_Click(object sender, EventArgs e)
{
string[] stringArray = { "demo", "sample", "test", "training" };
List<SampleClient> jobList =
(
from x in XDocument.Load(XML URL VALUE HERE).Root.Elements("job")
where x.Element("name").Value.Contains("-config") && x.Element("name").Value.StartsWith("oba-")
&& (!stringArray.Any(s => x.Element("name").Value.Contains(s)))
select new SampleClient
{
ClientId = getClientId((string)x.Element("name")),
JobName = (string)x.Element("name"),
JobUrl = (string)x.Element("url"),
}).ToList();
foreach (SampleClient job in jobList)
{
//Display the results in a textbox
txtResults.Text += "Job Name = " + job.JobName + " | Job URL = " + job.JobUrl + " | ";
}
}
In the query that obtains the version, I currently have:
var version = from item in doc.Descendants(ns + "properties")
select new SampleClient
{
ClientId = strClientId,
Version = (string)item.Element(ns + "product.version").Value
};
It is important to note that the URL source used in the second query is created using the ClientId that was obtained from the first. I could have a routine loop through and cleanup / merge objects, but this feels like a hack. Is there a cleaner way of handling this?
This is a sample of the XML from the first query:
<?xml version="1.0"?>
<allView>
<description>PROD</description>
<job>
<name>abc</name>
<url>http://ci-client.company.net/job/abc/</url>
<color>blue</color>
</job>
<job>
<name>acme</name>
<url>http://ci-client.company.net/job/acme/</url>
<color>blue</color>
</job>
</allView>
The second query URL is dynamically created by using the clientID from the results of the first. I am using this to load in a Maven POM file and grab version information. Here is a snippet of the XML from the POM file.
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company.xyz</groupId>
<artifactId>io</artifactId>
<version>6.11.7-ACME-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<product.version>6.11.7</product.version>
<db.version>1.4</db.version>
</properties>
<modules>
<module>policies</module>
<module>templates</module>
<module>assemble</module>
</modules>
</project>
I hope I understand your question..
This will merge them together (although, my test data only had ClientId, JobName and Version..). Query 1 contains ClientId and JobName, Query2 contains ClientId and Version.
IList<SampleClient> mergedList = firstQuery.Concat(secondQuery)
.ToLookup(x => x.ClientId)
.Select(x => x.Aggregate((query1, query2) =>
new SampleClient() {
ClientId = query1.ClientId,
JobName = query1.JobName,
Version = query2.Version
}
)
).ToList();
..possibly not as efficient as your manual method.. however I have nothing to benchmark against..
Try this:
var jobList = from x in XDocument.Load(...
where ...
let clientId = getClientId((string)x.Element("name"))
select new SampleClient
{
ClientId = clientId,
JobName = (string)x.Element("name"),
JobUrl = (string)x.Element("url"),
Version = (string)XDocument
.Load(GetSecondQueryUrl(clientId))
.Root
.Element(pom4 + "properties")
.Element(pom4 + "product.version")
};

need help using LINQ:XML to create an instance of a class

I have a class which defines meteorological measurements with an API. I have created classes to store these measurements within an XML file. I have been able to write to the XML file, but am unable to read from it. I need to be able to read the data, then instantiate the Measurement Class via the constructor, and then add these instances to a List
Here is the code for the Measurement constructor
public Measurement(string longit, string latitud, string cty, string pcode, DateTime dte, decimal huy, decimal pre, string windDir, int windSp) : base(longit, latitud, cty,pcode)
{
dte = date;
huy = humidity;
pre = precipitation;
windDir = windDirection;
windSp = windSpeed;
}
and here is a sample of the XML file
<Measurement>
<longitude>-76.2858726</longitude>
<latitude>36.8507689</latitude>
<city>Thetford</city>
<postcode>IP24 1ED</postcode>
<date>01/04/2011</date>
<windDirection>SE</windDirection>
<windSpeed>8</windSpeed>
<humidity>2.6</humidity>
<precipitation>0.0</precipitation>
</Measurement>
And here is the method I have written to read the file, and make an instance of the class Measurement. I receive no errors, however have not managed to create the List<>
public List<Measurement> GetList()
{
List<Measurement> measurements = new List<Measurement>();
XDocument doc = XDocument.Load(#"C:\Users\Sonya\Documents\Visual Studio 2010\Projects\WeatherAPI\WeatherAPI\measurement_store.xml");
XElement root = doc.Root;
var d = (from s in root.Descendants("Measurement")
select new Measurement
{
Longitude = s.Element("longitude").Value,
Latitude = s.Element("latitude").Value,
City = s.Element("city").Value,
Postcode = s.Element("postcode").Value,
Date = DateTime.Parse(s.Element("date").Value),
Humidity = decimal.Parse(s.Element("humidity").Value),
Precipitation = decimal.Parse(s.Element("precipitation").Value),
WindSpeed = Convert.ToInt32(s.Element("windSpeed").Value),
WindDirection = s.Element("windDirection").Value,
}).ToList();
d.ForEach(measurements.Add);
return measurements;
}//end GetList()
I've also written another method, of a slightly different format..
public List<Measurement> createXMLListMeasurements()
{
//load the xml document
XDocument doc = XDocument.Load(#"C:\Users\Sonya\Documents\Visual Studio 2010\Projects\WeatherAPI\WeatherAPI\measurement_store.xml");
//XElement root = doc.Root;
//instantiate a new list of measurements
List<Measurement> measurements = new List<Measurement>();
//get a list of measurement elements
var d = from s in doc.Descendants("Measurement")
//where
select new
{ //assign Measurement variables to data from xml doc
Longitude = (string)s.Element("longitude").Value,
Latitude = (string)s.Element("latitude").Value,
City = (string)s.Element("city").Value,
Postcode = (string)s.Element("postcode").Value,
Date = DateTime.Parse(s.Element("date").Value),
Humidity = decimal.Parse(s.Element("humidity").Value),
Precipitation = decimal.Parse(s.Element("precipitation").Value),
WindSpeed = Convert.ToInt32(s.Element("windSpeed").Value),
WindDirection = (string)s.Element("windDirection").Value,
};
foreach (var s in d)
{ //for each element found, instantiate Measurements class, and add to the measurements list.
Measurement m = new Measurement(s.Longitude, s.Latitude, s.City, s.Postcode, s.Date, s.Humidity, s.Precipitation, s.WindDirection, s.WindSpeed);
measurements.Add(m);
}
return measurements;
}
Apologies if these questions seem silly, am VERY new to LINQ and XML, so finding my way very slowly..any help much appreciated! A console application calls this method for testing and produces nothing but
WeatherAPI.Measurement
WeatherAPI.Measurement
Help? thanks!
Overall, your code looks fine. As Jon Skeet pointed out in his comment, you don't need to bother adding each element to a list -- you can simply return the result of the query after calling .ToList().
Most likely, there's either something wrong with your xml file, or you're reading it incorrectly.
If your xml file is truly just:
<Measurement>
<longitude>-76.2858726</longitude>
<latitude>36.8507689</latitude>
<city>Thetford</city>
</Measurement>
Then your code won't work, because the root of the xml file is Measurement. Therefore, calling doc.Root.Descendants("Measurement") will give you 0 results. Instead, your xml file should have a unique root element, for example:
<root>
<Measurement>
<longitude>-76.2858726</longitude>
<latitude>36.8507689</latitude>
<city>Thetford</city>
</Measurement>
<Measurement>
<longitude>-71.2858726</longitude>
<latitude>32.1507689</latitude>
<city>Other City</city>
</Measurement>
</root>
Furthermore, you don't need to bother obtaining the Root of the xml document. If all you want to do is find elements named Measurement, just say doc.Descendants("Measurement").
Try this code with the above xml file:
void Main()
{
var measurements = GetMeasurements(#"C:\path\to\file\measurements.xml");
}
public List<Measurement> GetMeasurements(string path)
{
XDocument doc = XDocument.Load(path);
return (from s in doc.Descendants("Measurement")
select new Measurement
{
Longitude = Convert.ToDecimal(s.Element("longitude").Value),
Latitude = Convert.ToDecimal(s.Element("latitude").Value),
City = s.Element("city").Value,
}).ToList();
}
public class Measurement
{
public decimal Longitude { get; set; }
public decimal Latitude { get; set; }
public string City { get; set; }
}
When I run it in LINQPad, I get the following result:

Categories