Need help parsing XML feed in Windows Phone - c#

I have an XML feed that I've retrieved via httpwebrequest, and I'm having problems parsing the feed since its unlike the times I've tried this in the past. So far I have the url
http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni&r=N
which I've stored in
XDocument doc = XDocument.Parse(feedString);
And I know that when I've dumped that all in a listbox for debugging purposes, I get everything there, I'm just having problems parsing the feed:
<body copyright="All data copyright San Francisco Muni 2012.">
<route tag="N" title="N-Judah" color="003399" oppositeColor="ffffff" latMin="37.7601699" latMax="37.7932299" lonMin="-122.5092" lonMax="-122.38798">
<stop tag="5240" title="King St & 4th St" lat="37.7760599" lon="-122.39436" stopId="15240"/>
<stop tag="5237" title="King St & 2nd St" lat="37.7796199" lon="-122.38982" stopId="15237"/>
<stop tag="7145" title="The Embarcadero & Brannan St" lat="37.7846299" lon="-122.38798" stopId="17145"/>
<stop tag="4510" title="Embarcadero Folsom St" lat="37.7907499" lon="-122.3898399" stopId="14510"/>
<stop tag="5629" title="Tunnel Entry Point Inbound Nea" lat="37.79279" lon="-122.39126" stopId="15629"/>
And so on and so on
I'd like to store each attribute in each stop tag into an array, but I'm completely stumped as to how I'd begin even.
Thanks
Update:
I think I got it to work on that first msdn link, but this only gets the first line:
using (XmlReader reader = XmlReader.Create(new StringReader(feed)))
{
reader.ReadToFollowing("stop");
reader.MoveToFirstAttribute();
string tag = reader.Value;
reader.MoveToNextAttribute();
string title = reader.Value;
reader.MoveToNextAttribute();
string lat = reader.Value;
reader.MoveToNextAttribute();
string lon = reader.Value;
}
How would I loop through each stop with that code above?
Thanks
EDIT: #2
This loop works, but keeps showing the first row of stop attributes:
using (XmlReader reader = XmlReader.Create(new StringReader(feed)))
{
reader.ReadToFollowing("stop");
while (reader.MoveToNextAttribute())
{
// Move the reader back to the element node.
//reader.ReadToFollowing("stop");
reader.MoveToFirstAttribute();
string tag = reader.Value;
MessageBox.Show(tag);
reader.MoveToNextAttribute();
string title = reader.Value;
MessageBox.Show(title);
reader.MoveToNextAttribute();
string lat = reader.Value;
MessageBox.Show(lat);
reader.MoveToNextAttribute();
string lon = reader.Value;
MessageBox.Show(lon);
}
reader.MoveToElement();
}
I feel like I'm so close to figuring it out.

Here is a complete solution with the help of Linq. Just see what result contains
using (WebClient w = new WebClient())
{
string xml = w.DownloadString("http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni&r=N");
XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Descendants("stop")
.Select(n => new
{
Tag = (string)n.Attribute("tag"),
Title = (string)n.Attribute("title"),
Lat = (string)n.Attribute("lat"),
Lon = (string)n.Attribute("lon"),
StopId = (string)n.Attribute("stopId")
})
.ToArray();
}

Here is something you can use:
XmlReader xmlReader = XmlReader.Create("http://webservices.nextbus.com/service/publicXMLFeed? command=routeConfig&a=sf-muni&r=N");
List<string> aTitle= new List<string>();
// Add as many as attributes you have in your "stop" element
while (xmlReader.Read())
{
//keep reading until we see your element
if (xmlReader.Name.Equals("stop") && (xmlReader.NodeType == XmlNodeType.Element))
{
string title = xmlReader.GetAttribute("title");
aTitle.Add(title);
// Add code for all other attribute you would want to store in list.
}
}
Finally call the list and based on index you can get all the items.

There are many way to parse XML file. Here is one simple approach:
XDocument doc = XDocument.Load(feedString);
var stops = doc.Document.Descendants(XName.Get("route"));
// Loop over all stops in the XML
foreach (var stop in stops)
{
}
I'm not sure what do you mean by "store each attribute in each stop tag into an array", but I would define a type:
class RouteStop
{ // make setters private and init them in ctor to make it immutable
public string Tag {get; set;} //maybe int ?
public string Title {get; set;}
public double Latitude {get; set;}
public double Longitude {get; set;}
public int ID {get; set;}
}
Then define a List of RouteStop
List<RouteStop> routeStops = new List<RouteStop>();
and simply in the create object in foreach loop
foreach (var stop in stops)
{
var tag = stop.Attribute("tag").Value;
var title = stop.Attribute("title").Value;
var long = double.Parse(stop.Attribute("lon").Value,
CultureInfo.InvariantCulture);
//etc
routeStops.add(new RouteStop() { Tag = tag } //and so on
}

Related

Create XML file with custom formatting

I need to create an XML file with line breaks and tabs in Attributes and on few tags as well. So I tried like below.
string xmlID = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<PersonLib Version=\"1.0\"></PersonLib>";
XDocument doc = XDocument.Parse(xmlID);//, LoadOptions.PreserveWhitespace);
XElement device = doc.Root;
using (StringWriter str = new StringWriter())
using (XmlTextWriter xml = new XmlTextWriter(str))
{
xml.Formatting=Formatting.Indented;
xml.WriteStartElement("Details");
xml.WriteWhitespace("\n\t");
xml.WriteStartElement("Name");
xml.WriteWhitespace("\n\t\t");
xml.WriteStartElement("JohnDoe");
xml.WriteAttributeString("DOB", "10");
xml.WriteAttributeString("FirstName", "20");
xml.WriteAttributeString("LastName", "40");
xml.WriteAttributeString("\n\t\t\tAddress", "50");
xml.WriteAttributeString("\n\t\t\tPhoneNum", "60");
xml.WriteAttributeString("\n\t\t\tCity", "70");
xml.WriteAttributeString("\n\t\t\tState", "80");
xml.WriteAttributeString("\n\t\t\tCountry", "90");
//xml.WriteWhitespace("\n\t\t");
xml.WriteEndElement();
xml.WriteWhitespace("\n\t");
xml.WriteEndElement();
xml.WriteWhitespace("\n");
xml.WriteEndElement();
Console.WriteLine(str);
device.Add(XElement.Parse(str.ToString(), LoadOptions.PreserveWhitespace));
File.WriteAllText("MyXML.xml", device.ToString());
I can get the XML generated in format I need but the issue comes when I try to add it to the parent XMLElement device in this case. The formatting is all gone despite LoadOptions.PreserveWhitespace.
I get
<PersonLib Version="1.0">
<Details>
<Name>
<JohnDoe DOB="10" FirstName="20" LastName="40" Address="50" PhoneNum="60" City="70" State="80" Country"90" />
</Name>
</Details>
</PersonLib >
while I need
<PersonLib Version="1.0">
<Details>
<Name>
<JohnDoe DOB="10" FirstName="20" LastName="40"
Address="50"
PhoneNum="60"
City="70"
State="80"
Country="90" />
</Name>
</Details>
</PersonLib >
Not sure what am I missing.
You should take a look at this question: xdocument save preserve white space inside tags
LoadOptions.PreserveWhitespace (LoadOptions Enum)
If you preserve white space when loading, all insignificant white
space in the XML tree is materialized in the XML tree as is. If you do
not preserve white space, then all insignificant white space is
discarded.
This gives the impression that 'insignificant' whitespace between attributes would be preserved.
If however you look at XElement.Parse Method you see this:
If the source XML is indented, setting the PreserveWhitespace flag in
options causes the reader to read all white space in the source XML.
Nodes of type XText are created for both significant and insignificant
white space.
Looking at the class hierarchy you can see that XAttribute does not inherit from XNode. The long and the short of that is whitespace between attributes are not preserved. If they were you would still have to disable formatting on output (something like ToString(SaveOptions.DisableFormatting)).
I don't think that attributes were designed to be used as you have, but it is a very common usage. There is considerable diversity of opinion about this (see: Attribute vs Element)
Either way it sounds like you are stuck with both the design and format of what you were given. Unfortunately, this means you are also stuck with having to create a custom formatter to get the output you need.
Note the following code is meant only as an example of one possible way to implement code that creates the format you ask about.
using System;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace FormatXml {
class Program {
static String OutputElement(int indentCnt, XElement ele) {
StringBuilder sb = new StringBuilder();
var indent = "".PadLeft(indentCnt, '\t');
var specialFormat = (ele.Parent == null) ? false : ((ele.Parent.Name == "Name") ? true : false);
sb.Append($"{indent}<{ele.Name}");
String FormatAttr(XAttribute attr) {
return $"{attr.Name} = '{attr.Value}'";
}
String FormatAttrByName(String name) {
var attr = ele.Attributes().Where(x => x.Name == name).FirstOrDefault();
var rv = "";
if (attr == null) {
rv = $"{name}=''";
}
else {
rv = FormatAttr(attr);
}
return rv;
}
if (specialFormat) {
var dob = FormatAttrByName("DOB");
var firstName = FormatAttrByName("FirstName");
var lastName = FormatAttrByName("LastName");
var address = FormatAttrByName("Address");
var phoneNum = FormatAttrByName("PhoneNum");
var city = FormatAttrByName("City");
var state = FormatAttrByName("State");
var country = FormatAttrByName("Country");
sb.AppendLine($"{dob} {firstName} {lastName}");
var left = ele.Name.LocalName.Length + 5;
var fill = indent + "".PadLeft(left);
sb.AppendLine($"{fill}{address}");
sb.AppendLine($"{fill}{phoneNum}");
sb.AppendLine($"{fill}{city}");
sb.AppendLine($"{fill}{state}");
sb.AppendLine($"{fill}{country} />");
}
else {
foreach (var attr in ele.Attributes()) {
sb.AppendFormat(" {0}", FormatAttr(attr));
}
}
sb.AppendLine(">");
foreach (var e in ele.Elements()) {
sb.Append(OutputElement(indentCnt + 1, e));
}
sb.AppendLine($"{indent}</{ele.Name}>");
return sb.ToString();
}
static void Main(string[] args) {
var txtEle = #"
<Details>
<Name>
<JohnDoe DOB = '10' FirstName = '20' LastName = '40'
Address = '50'
PhoneNum = '60'
City = '70'
State = '80'
Country = '90' />
</Name>
</Details>";
var plib = new XElement("PersonLib");
XDocument xdoc = new XDocument(plib);
var nameEle = XElement.Parse(txtEle, LoadOptions.PreserveWhitespace);
xdoc.Root.Add(nameEle);
var xml = OutputElement(0, (XElement)xdoc.Root);
Console.WriteLine(xml);
}
}
}

XML response processing

I am getting raw XML response from eBay API call. Now I have to grab values from XML and add those values to ViewModel object then return data to my View. I have already tried few things bellow but I am unable to grab XML response items and assign to ViewModel. Please check each part of code below and let me know if you have any solution. I don't need working code coz i know you cant test it since its uncompleted. Just simple hints would enough, however, i am new with c#. Thanks a lot in advance
Controller:
public ActionResult Search(string OperationName, string calltype, string page, string keywords, string type, string location, string condition, string min, string max, string negative, string minFeedback, string maxFeedback, string drange, string categoryId)
{
string AppId = "KavinHim-BestProd-PRD-785446bf1-666"; //api configs
string BaseUrl = "http://svcs.ebay.com/services/search/FindingService/v1?OPERATION-NAME="; //base url api end point
if (calltype == "categoryClick")
{
string Url = BaseUrl + OperationName + "&SERVICE-VERSION=1.0.0&SECURITY-APPNAME=" + AppId + "&RESPONSE-DATA-FORMAT=XML&REST-PAYLOAD&categoryId=" + categoryId + "&paginationInput.entriesPerPage=2&paginationInput.pageNumber=" + page + "";
var client = new RestClient(Url);
var request = new RestRequest(Method.GET);
request.Method = Method.GET;
request.Parameters.Clear();
var xmlResponse = client.Execute(request).Content;
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(new WebClient().DownloadString(xmlResponse));
var items = new List<EbayDataViewModel>();
foreach (var item in items)
{
//add items from xml data to EbayDataViewModel object
}
}
return View();
}
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WebApplication2.Models;
namespace WebApplication2.ViewModels
{
public class EbayDataViewModel
{
public string EbayImageUrl { get; set; }
public string EbayTitle { get; set; }
public string NumberOfSales { get; set; }
public string NumberOfWatch { get; set; }
}
}
XML Response:
<?xml version="1.0" encoding="UTF-8"?>
-<findItemsByCategoryResponse xmlns="http://www.ebay.com/marketplace/search/v1/services">
<ack>Success</ack>
<version>1.13.0</version>
<timestamp>2017-10-14T08:27:04.876Z</timestamp>
-<searchResult count="2">
-<item>
<itemId>332390426668</itemId>
<title>حجاب روحاني يتحرك,حجاب الغزال أوالظبي ,أقوى حجاب للمحبة والزواج ,حرز حي يتفاعل </title>
<globalId>EBAY-US</globalId>
-<primaryCategory>
<categoryId>162918</categoryId>
<categoryName>Islamic</categoryName>
</primaryCategory>
<galleryURL>http://thumbs1.ebaystatic.com/m/mf7QDRtgSn83eQF23aLFD_Q/140.jpg</galleryURL>
<viewItemURL>http://www.ebay.com/itm/-/332390426668</viewItemURL>
<paymentMethod>PayPal</paymentMethod>
<autoPay>true</autoPay>
<postalCode>04468</postalCode>
<location>Old Town,ME,USA</location>
<country>US</country>
-<shippingInfo>
<shippingServiceCost currencyId="USD">0.0</shippingServiceCost>
<shippingType>FlatDomesticCalculatedInternational</shippingType>
<shipToLocations>Worldwide</shipToLocations>
<expeditedShipping>false</expeditedShipping>
<oneDayShippingAvailable>false</oneDayShippingAvailable>
<handlingTime>1</handlingTime>
</shippingInfo>
-<sellingStatus>
<currentPrice currencyId="USD">350.0</currentPrice>
<convertedCurrentPrice currencyId="USD">350.0</convertedCurrentPrice>
<sellingState>Active</sellingState>
<timeLeft>P10DT14H19M17S</timeLeft>
</sellingStatus>
-<listingInfo>
<bestOfferEnabled>true</bestOfferEnabled>
<buyItNowAvailable>false</buyItNowAvailable>
<startTime>2017-09-24T22:46:21.000Z</startTime>
<endTime>2017-10-24T22:46:21.000Z</endTime>
<listingType>StoreInventory</listingType>
<gift>false</gift>
<watchCount>6</watchCount>
</listingInfo>
<returnsAccepted>true</returnsAccepted>
<galleryPlusPictureURL>http://galleryplus.ebayimg.com/ws/web/332390426668_1_1_1.jpg</galleryPlusPictureURL>
<isMultiVariationListing>false</isMultiVariationListing>
<topRatedListing>false</topRatedListing>
</item>
-<item>
<itemId>382249935959</itemId>
<title>Circa 900 AD Authentic Viking Bronze Snake Bracelet Found In Latvia Excavation</title>
<globalId>EBAY-US</globalId>
-<primaryCategory>
<categoryId>162920</categoryId>
<categoryName>Viking</categoryName>
</primaryCategory>
<galleryURL>http://thumbs4.ebaystatic.com/m/mf73oCtiHN2GGSZlIY0VP7Q/140.jpg</galleryURL>
<viewItemURL>http://www.ebay.com/itm/Circa-900-AD-Authentic-Viking-Bronze-Snake-Bracelet-Found-Latvia-Excavation-/382249935959</viewItemURL>
<paymentMethod>PayPal</paymentMethod>
<autoPay>true</autoPay>
<postalCode>80932</postalCode>
<location>Colorado Springs,CO,USA</location>
<country>US</country>
-<shippingInfo>
<shippingServiceCost currencyId="USD">0.0</shippingServiceCost>
<shippingType>Free</shippingType>
<shipToLocations>Worldwide</shipToLocations>
<expeditedShipping>false</expeditedShipping>
<oneDayShippingAvailable>false</oneDayShippingAvailable>
<handlingTime>1</handlingTime>
</shippingInfo>
-<sellingStatus>
<currentPrice currencyId="USD">52.0</currentPrice>
<convertedCurrentPrice currencyId="USD">52.0</convertedCurrentPrice>
<sellingState>Active</sellingState>
<timeLeft>P28DT14H3M39S</timeLeft>
</sellingStatus>
-<listingInfo>
<bestOfferEnabled>false</bestOfferEnabled>
<buyItNowAvailable>false</buyItNowAvailable>
<startTime>2017-10-12T22:30:43.000Z</startTime>
<endTime>2017-11-11T22:30:43.000Z</endTime>
<listingType>FixedPrice</listingType>
<gift>false</gift>
<watchCount>1</watchCount>
</listingInfo>
<returnsAccepted>true</returnsAccepted>
<isMultiVariationListing>false</isMultiVariationListing>
<topRatedListing>true</topRatedListing>
</item>
</searchResult>
-<paginationOutput>
<pageNumber>1</pageNumber>
<entriesPerPage>2</entriesPerPage>
<totalPages>27113</totalPages>
<totalEntries>54226</totalEntries>
</paginationOutput>
<itemSearchURL>http://www.ebay.com/sch/37903/i.html?_ddo=1&_ipg=2&_pgn=1</itemSearchURL>
</findItemsByCategoryResponse>
If you're not using .NET Version 3.0 or lower, It's highly recommend to use XDocument over XmlDocument: reference link
Anyway, you can use .Descendants() to get all descendants, and then compare the local name of all its elements to see if there is an element whose local name matches the name of item.
(Tested)
var items = new List<EbayDataViewModel>();
// You can directly plug the url in with .Load() method.
// No need to create HttpClient to download the response as string and then
// parse.
XDocument xdoc = XDocument.Load(url);
// Since you're only interested in <item> collections within <searchResult>
var searchResultItems = xdoc.Descendants()
.Where(x => x.Name.LocalName == "item");
foreach (var sri in searchResultItems)
{
// Get all child xml elements
var childElements = sri.Elements();
var itemId = childElements.FirstOrDefault(x => x.Name.LocalName == "itemId");
var title = childElements.FirstOrDefault(x => x.Name.LocalName == "title");
//add items from xml data to EbayDataViewModel object
items.Add(new EbayDataViewModel {
EbayTitle = title == null? Stirng.Empty : title.Value,
...
});
}
return items;
Since there are lots of checking and comparison, writing extention methods for those would be useful.
public static class XElementExtensions
{
public static XElement LocalNameElement(this XElement parent, string localName)
{
return parent.Elements().FirstOrDefault(x => x.Name.LocalName == localName);
public static IEnumerable<XElement> LocalNameElements(this XElement parent, string localName)
{
return parent.Elements().Where(x => x.Name.LocalName == localName);
}
public static string LocalNameElementValue(this XElement parent, string localName)
{
var element = parent.LocalNameElement(localName);
return element == null? String.Empty : element.Value;
}
...
}
One way would be accessing the item node values from your XmlDocument using XPath query strings. You'll probably want to loop over the response items and create new ViewModel objects to populate your list of ViewModels:
var items = new List<EbayDataViewModel>();
var responseItems = xdoc.SelectNodes("item");
foreach (responseItem in responseItems)
{
items.Add(new EbayDataViewModel()
{ EbayImageUrl = responseItem.SelectSingleNode("galleryURL").InnerText,
EbayTitle = responseItem.SelectSingleNode("title").InnerText,
NumberOfSales = responseItem.SelectSingleNode("").InnerText,
NumberOfWatch = responseItem.SelectSingleNode("").InnerText
});
}
Note: I couldn't find anything that looked like "NumberOfSales or NumberOfWatch" in the XML Response, so those XPaths will need to be filled in.
See XmlDocument.SelectNodes for documentation on this method.

C# reading XML , how to get the value

I am new to XML. Need some help.
I can get pro NAME fine but
How do I get the value of scode? JDK...blah
<pro NAME="JK1233k">
<scode ID="A">JDK-ORPLL-PDILL</scode>
</pro>
XmlReader reader = XmlReader.Create("file.xml");
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "pro"))
{
Console.WriteLine(reader["NAME"]);
}
else if((reader.NodeType == XmlNodeType.Element) && (reader.Name == "scode"))
{
Console.WriteLine(reader["ID"]);
//what do I put here to get the value????
}
}
reader.Close();
What you're looking for is:
Console.WriteLine(reader.ReadInnerXml());
I personally prefer LINQ to XML. If you haven't looked into it, you should. You can achieve the same thing in a cleaner manner.
at the start of your c# file put the following:
using System.Linq;
using System.Xml.Linq; // loads the linq to xml part
Most XML files are much bigger than just one element. So for that, your code would be something like this:
// Load XML file as an IEnumerable. This allows you to query it.
var xmlDoc = XDocument.Load(file)
.Elements("pro")
.Select(pro => new
{
Name = pro.Attribute("NAME").Value,
Scode = pro.Elements("scode").Select(scode => new
{
ID = scode.Attribute("ID").Value,
Val = scode.Value
})
});
// loop through each <pro> element
foreach (var pro in xmlDoc)
{
// Get Pro Name
Console.WriteLine(pro.Name);
// loop through each <scode> element inside <pro>
foreach(var scode in pro.Scode)
{
// Get Scode ID:
Console.WriteLine(scode.ID);
// Get Scode Value:
Console.WriteLine(scode.Val);
}
}
If your XML is only a SINGLE element, you can do this:
// Load XML file:
var pro = XElement.Load("file.xml");
// Get Pro Name
pro.Attribute("NAME").Value;
// Get Scode ID:
pro.Element("scode").Attribute("ID").Value;
// Get Scode Value:
pro.Element("scode").Value;
Consider the following code snippet...
XDocument doc = XDocument.Load("file.xml");
foreach (XElement element in doc.Descendants("pro"))
{
Console.WriteLine(element.Attribute("NAME").Value);
}
foreach (XElement element in doc.Descendants("scode"))
{
Console.WriteLine(element.Value);
}
Good Luck!

c# code for getting xml elements

I have following xml file:
<os:tax>
<os:cat name="abc" id="1">
<os:subcat name="bcd" id="11">
<os:t name="def" id="111">
<os:cut name="hello" id="161" cutURL="/abc/a.html"/>
<os:cut name="hello2" id="162" cutURL="/abc1/a1.html"/>
<os:cut name="hello3" id="163" cutURL="/abc4/a3.html"/>
</os:t>
</os:subcat>
</os:cat>
<os:cat name="def" id="2">
<os:subcat name="bcd" id="22">
<os:t name="def" id="222">
<os:cut name="hello" id="171" cutURL="/abcs/a.html"/>
<os:cut name="hello2" id="172" cutURL="/abcs1/a1.html"/>
<os:cut name="hello3" id="173" cutURL="/abcs4/a3.html"/>
</os:t>
</os:subcat>
</os:cat>
</os:tax>
Its a bigger file with lot of os:cat under it. I need to get string value for:
os:cat -> id , name
os:subcat -> id, name
os: t -> id, name
os: cut -> id, name, cutURL
I have this so far:
XmlNodeList tax = xmlDoc.GetElementsByTagName("os:tax");
foreach (XmlNode node in tax)
{
XmlElement cat = (XmlElement)node;
// than get string values here?
}
Is this correct? Can anyone show me efficient way to do this? Or right way to do this easily?
Here's a sample for LINQ to XML - but I strongly suggest you look for full LINQ to XML tutorials. (And get to grips with the rest of LINQ...)
(EDIT: I hadn't spotted the t part before.)
XDocument doc = XDocument.Load("tax.xml");
XNamespace os = "http://something"; // You haven't included the declaration...
foreach (XElement cat in doc.Descendants(os + "cat"))
{
int catId = (int) cat.Attribute("id");
string catName = (string) cat.Attribute("name");
foreach (XElement subcat in cat.Elements(os + "subcat"))
{
int subId = (int) subcat.Attribute("id");
string subName = (string) subcat.Attribute("name");
foreach (XElement t in subcat.Elements(os + "t"))
{
int tId = (int) t.Attribute("id");
string tName = (string) t.Attribute("name");
foreach (XElement cut in t.Elements(os + "cut"))
{
string cutId = (int) cut.Attribute("id");
string cutName = (string) cut.Attribute("name");
string cutUrl = (string) cut.Attribute("cutURL");
// Use the variables here
}
}
}
}
This assumes there's only one subcat for each cat - I don't know if that's correct.
You may want to express this as a LINQ query instead... it depends on what you need to do.
Here's a LINQ query version - having looked at everything you're using, I think this makes more sense:
XDocument doc = XDocument.Load("tax.xml");
XNamespace os = "http://something"; // You haven't included the declaration...
var query = from cat in doc.Descendants(os + "cat")
from subcat in cat.Elements(os + "subcat")
from t in subcat.Elements(os + "t")
from cut in t.Elements(os + "cut")
select new
{
CatId = (int) cat.Attribute("id"),
CatName = (string) cat.Attribute("name"),
SubCatId = (int) subcat.Attribute("id"),
SubCatName = (string) subcat.Attribute("name"),
TId = (int) t.Attribute("id"),
TName = (string) t.Attribute("name"),
CutId = (int) cut.Attribute("id")
CutName = (string) cut.Attribute("name")
CutUrl = (string) cut.Attribute("cutURL")
};
Note that I've converted all the ID values to int rather than string. You could convert them to strings instead, of course, but if they are all integers, it makes sense to parse them as such.
Jon's suggestion to use LINQ to XML is the way to go, but I've included the old way below. My XPath is a little (very) rusty, so forgive me if there are any mistakes:
var doc = new XmlDocument(); //your document
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
xmlnsManager.AddNamespace("os", "http://bla");
foreach (XmlNode node in doc.SelectNodes("//os:subcat/os:t/os:cut", xmlnsManager))
{
string value = node.Attributes.GetNamedItem("name").Value;
}
See this article if you need more help: http://support.microsoft.com/kb/318545
Consider using XElement along with Lambda expression.
XNamespace osNs = "http://xml.com"; // Like Jon said, you haven't included the namespace url
XElement taxElement = XElement.Load("path/to/your/xml/file");
foreach(var cat in taxElement.Decendents(osNs + "cat"))
{
Console.WriteLine(cat.Attribute("id").Value);
foreach(var subcat in cat.Decendents(osNs + "subcat"))
{
Console.WriteLine(subcat.Attribute("id").Value);
foreach(var t in subcat.Decendents(osNs + "t"))
{
Console.WriteLine(t.Attribute("id").Value);
foreach(var cut in t.Decendents(osNs + "cut"))
{
Console.WriteLine(cut.Attribute("id").Value);
Console.WriteLine(cut.Attribute("name").Value);
Console.WriteLine(cut.Attribute("cutURL").Value);
}
}
}
}
It's just capturing one node at by another. If you want to get all the curURL then you can just write something like this:
foreach(var cut in taxElement.Decendents(osNs + "cut"))
{
Console.WriteLine(cut.Attribute("cutURL"));
}
Even you can use Lambda like if you want something like all os:cut where os:subcat id = 22
taxElement.Decendents("osNs + "subcat").Where(p => p.Attribute("id").Value == "22").Decendents(osNs + "cut");
Please go through some tutorial on LINQ to XML or something on XElement.
Hope this helps!

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