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.
Related
I am using c# .net 4.6 xpath to search for node with id value and when found create a new attribute for the parent element in place. I have a list of such id values that I need to iterate over and create attributes to produce a new xml document. I have attempted the following but does not work. The XPathNavigator.MoveTo method appears to replace the source navigator with the moved to element thereby loosing all other content. Is this not the right way to achieve this ? Could you please advice ?
See code snippet below:
publicationDoc.LoadXml(publicationXPathNav.OuterXml);
XPathNavigator publicationNav = publicationDoc.CreateNavigator();
foreach (IListBlobItem item in contentDirectory.ListBlobs())
{
var blob = (CloudBlob)item;
string contentId = blob.Name;
XPathNavigator contentRefNav = publicationNav.SelectSingleNode($#"//releaseItem/contentRef[id = {"'" + contentId + "'"}]/..");
if (contentRefNav != null)
{
publicationNav.MoveTo(contentRefNav); // here publicationNav gets replaced by contentRefNav
publicationNav.CreateAttribute("", "fileName", "", contentFileName);
}
}
// once finished with the foreach I was hoping to be able to save the publicationNav.OuterXml to a new file with the newly added attributes.
Here is a small cut down sample source xml data :
<publicationsRoot>
<publication>
<urn>iso:pub:std:FDIS:74824</urn>
<releaseItems>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92764155</id>
</contentRef>
</releaseItem>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92802320</id>
</contentRef>
</releaseItem>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92801989</id>
</contentRef>
</releaseItem>
<releaseItems>
</publication>
</publicationsRoot>
Try xml linq with a dictionary
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication167
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, XElement> dict = doc.Descendants("id")
.GroupBy(x => (string)x, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
string id = "92764155";
string filename = "filename";
if (dict.ContainsKey(id))
{
dict[id].SetAttributeValue("filename", filename);
}
}
}
}
I managed to resolve this by not using XPathNavigator and relying only on XMLDocuent. It appears that XPathNavigator is more suitable for relative paths whereas my requirement was to search specific nodes and update the xml document in place.
publicationDoc.LoadXml(publicationXPathNav.OuterXml);
foreach (IListBlobItem item in contentDirectory.ListBlobs())
{
var blob = (CloudBlob)item;
string contentId = blob.Name;
XmlNode contentRefNode = publicationDoc.SelectSingleNode($#"//releaseItem/contentRef[id = {"'" + contentId + "'"}]/..");
if (contentRefNode != null)
{
XmlAttribute fileName = publicationDoc.CreateAttribute("fileName");
fileName.Value = contentFileName + contentFileExt;
contentRefNode.Attributes.SetNamedItem(fileName);
}
}
// once finished with the foreach I was hoping to be able to save the publicationNav.OuterXml to a new file with the newly added attributes.
Thanks for all the answers. I will certainly take those on board.
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);
}
}
}
My XML File is structured as follows:
<File>
<Setting1></Setting1>
<Setting2></Setting2>
<Options>
<Option>
<NameStartsWith>Br</NameStartsWith>
<Data>1234</Data>
</Option>
<Option>
<NameStartsWith>Ch</NameStartsWith>
<Data>4567</Data>
</Option>
</Options>
</File>
What I would like to do is use LINQ for something like the below..
String Name = "Brian";
if(Name.StartsWith(LINQ.Any.NameStartsWith)))
{
Console.WriteLine("The Answer is: " 1234);
}
At present I perform the above by looping through the <Option> fields with foreach (XElement xe in Tests). But the real XML file is a lot more detailed than this and the loops are getting unmanageable. I would ideally like to use LINQ to search all fields at once and make it a simple if or statement.
Use LINQ to Xml
string name = "Brian";
XDocument doc = XDocument.Load(yourXmlFile);
var matches = doc.Root
.Descendants("Option")
.Where(option => name.StartsWith(option.Element("NameStartsWith").Value))
.Select(option => option.Element("Data").Value);
foreach(var data in matches)
{
Console.WriteLine("The Answer is: {data}");
}
XContainer.Descendants Method (XName) will return all elements with name passed as parameter from all hierarchical levels of current XElement.
If element NameStartsWith is optional inside Option, then just add checking for null in the chain of LINQ methods. XElement.Element(XName name) will return null if no such element exists.
var matches = doc.Root
.Descendants("Option")
.Where(option => option.Element("NameStartsWith") != null)
.Where(option => name.StartsWith(option.Element("NameStartsWith").Value))
.Select(option => option.Element("Data").Value);
If Option element contain more then one other elements which need to be selected, then create a class which represent all needed data and fill it inside Select method
public class Option
{
public string NameStartsWith {get; set; }
public string Data {get; set; }
public string ElementOne {get; set; }
public string ElementTwo {get; set; }
}
var matches = doc.Root
.Descendants("Option")
.Where(option => option.Element("NameStartsWith") != null)
.Where(option => name.StartsWith(option.Element("NameStartsWith").Value))
.Select(option => new Option
{
NameStartsWith = option.Element("Data").Value,
Data = option.Element("Data").Value,
ElementOne = option.Element("ElementOne").Value,
ElementTwo = option.Element("ElementTwo").Value,
});
Of course you can use anonymous class instead of created one.
XPATH + Linq2Xml is also possible
string Name = "Brian";
var xDoc = XDocument.Parse(xmlstring); //or XDocument.Load(filename)
var matches = xDoc
.XPathSelectElements($"//Option/NameStartsWith[starts-with('{Name}', text())]");
I need to convert xml to dictionary. I've never done that before. Can you please show me a code example how to convert this xml to dictionary to get values like this:
Key: Vendor and Value: BankRed
Key: CustRef and Value: dfas16549464
Key: InvP and Value: 1, 12
Here is xml:
<root>
<Vendor name = "BankRed">
<CustRef>dfas16549464</CustRef>
<InvP> 1, 12</InvP>
</Vendor>
</root>
Your help will be appreciated. Thanks a lot!
I think you could do with clarifying a bit but this achieves the desired effect assuming that the Vendor attribute name should be the Key for the Vendor section, though you haven't been clear
XDocument xml = XDocument.Load("path to your xml file");
var dict = xml.Descendants("Vendors")
.Elements()
.ToDictionary(r => r.Attribute("name").Value, r => r.Value);
Assuming an XML structure:
<root>
<Vendors>
<Vendor name="BankRed">
<CustRef>dfas16549464</CustRef>
<InvP> 1, 12</InvP>
</Vendor>
</Vendors>
<Vendors>
<Vendor name="BankBlue">
<CustRef>foo</CustRef>
<InvP>bar</InvP>
</Vendor>
</Vendors>
</root>
You will get a Dictionary<string, string> with two elements that looks like: -
Key: BankRed
Values: dfas16549464 1, 12
Key: BankBlue
Values: foo bar
However, I think you're taking the wrong approach. A better idea would be to create a custom type Vendor to store this information, for example:
public class Vendor
{
public Vendor()
{ }
public string CustRef { get; set; }
public string VendorName { get; set; }
public string InvP { get; set; }
}
And the query would become:
var query = (from n in xml.Descendants("Vendors")
select new Vendor()
{
VendorName = n.Element("Vendor").Attribute("name").Value,
CustRef = n.Descendants("Vendor").Select(x => x.Element("CustRef").Value).SingleOrDefault(),
InvP = n.Descendants("Vendor").Select(x => x.Element("InvP").Value).SingleOrDefault()
}).ToList();
Which will give you a list of Vendors that look like this:
The data is now much easier to work with.
i've writen a code and tested it. i hope that's what you need, though your questiong isn't clear on why you need it:
the class Entity for the use of the dictionary:
public class Entity
{
private string CustRef;
private string InvP;
public Entity(string custRef, string invP)
{
CustRef = custRef;
InvP = invP;
}
}
and the convert code:
Dictionary<string, Entity> myTbl = new Dictionary<string, Entity>();
XmlDocument doc = new XmlDocument();
doc.Load(#"d:\meXml.xml");
XmlNode root = doc.FirstChild;
foreach (XmlNode childNode in root.ChildNodes)
{
myTbl[childNode.Attributes[0].Value] = new Entity(
childNode.FirstChild.InnerText,
childNode.LastChild.InnerText);
}
Try using below code:
Dictionary<int, string> elements = xml.Elements(ns + "root")
.Select(sp => new {
CustRef = (string)(sp.Attribute("CustRef")),
vendor = (string)(sp.Attribute("Vendor"))
})
.ToDictionary(sp => sp.CustRef, sp => sp.Vendor);
I've already read some posts and articles on how to deserialize xml but still haven't figured out the way I should write the code to match my needs, so.. I'm apologizing for another question about deserializing xml ))
I have a large (50 MB) xml file which I need to deserialize. I use xsd.exe to get xsd schema of the document and than autogenerate c# classes file which I put into my project. I want to get some (not all) data from this xml file and put it into my sql database.
Here is the hierarchy of the file (simplified, xsd is very large):
public class yml_catalog
{
public yml_catalogShop[] shop { /*realization*/ }
}
public class yml_catalogShop
{
public yml_catalogShopOffersOffer[][] offers { /*realization*/ }
}
public class yml_catalogShopOffersOffer
{
// here goes all the data (properties) I want to obtain ))
}
And here is my code:
first approach:
yml_catalogShopOffersOffer catalog;
var serializer = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
var reader = new StreamReader(#"C:\div_kid.xml");
catalog = (yml_catalogShopOffersOffer) serializer.Deserialize(reader);//exception occures
reader.Close();
I get InvalidOperationException: There is an error in the XML(3,2) document
second approach:
XmlSerializer ser = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
yml_catalogShopOffersOffer result;
using (XmlReader reader = XmlReader.Create(#"C:\div_kid.xml"))
{
result = (yml_catalogShopOffersOffer)ser.Deserialize(reader); // exception occures
}
InvalidOperationException: There is an error in the XML(0,0) document
third: I tried to deserialize the entire file:
XmlSerializer ser = new XmlSerializer(typeof(yml_catalog)); // exception occures
yml_catalog result;
using (XmlReader reader = XmlReader.Create(#"C:\div_kid.xml"))
{
result = (yml_catalog)ser.Deserialize(reader);
}
And I get the following:
error CS0030: The convertion of type "yml_catalogShopOffersOffer[]" into "yml_catalogShopOffersOffer" is not possible.
error CS0029: The implicit convertion of type "yml_catalogShopOffersOffer" into "yml_catalogShopOffersOffer[]" is not possible.
So, how to fix (or overwrite) the code to not get the exceptions?
edits: Also when I write:
XDocument doc = XDocument.Parse(#"C:\div_kid.xml");
The XmlException occures: unpermitted data on root level, string 1, position 1.
Here is the first string of the xml file:
<?xml version="1.0" encoding="windows-1251"?>
edits 2:
The xml file short example:
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2012-11-01 23:29">
<shop>
<name>OZON.ru</name>
<company>?????? "???????????????? ??????????????"</company>
<url>http://www.ozon.ru/</url>
<currencies>
<currency id="RUR" rate="1" />
</currencies>
<categories>
<category id=""1126233>base category</category>
<category id="1127479" parentId="1126233">bla bla bla</category>
// here goes all the categories
</categories>
<offers>
<offer>
<price></price>
<picture></picture>
</offer>
// other offers
</offers>
</shop>
</yml_catalog>
P.S.
I've already acccepted the answer (it's perfect). But now I need to find "base category" for each Offer using categoryId. The data is hierarchical and the base category is the category that has no "parentId" attribute. So, I wrote a recursive method to find the "base category", but it never finishes. Seems like the algorythm is not very fast))
Here is my code: (in the main() method)
var doc = XDocument.Load(#"C:\div_kid.xml");
var offers = doc.Descendants("shop").Elements("offers").Elements("offer");
foreach (var offer in offers.Take(2))
{
var category = GetCategory(categoryId, doc);
// here goes other code
}
Helper method:
public static string GetCategory(int categoryId, XDocument document)
{
var tempId = categoryId;
var categories = document.Descendants("shop").Elements("categories").Elements("category");
foreach (var category in categories)
{
if (category.Attribute("id").ToString() == categoryId.ToString())
{
if (category.Attributes().Count() == 1)
{
return category.ToString();
}
tempId = Convert.ToInt32(category.Attribute("parentId"));
}
}
return GetCategory(tempId, document);
}
Can I use recursion in such situation? If not, how else can I find the "base category"?
Give LINQ to XML a try. XElement result = XElement.Load(#"C:\div_kid.xml");
Querying in LINQ is brilliant but admittedly a little weird at the start. You select nodes from the Document in a SQL like syntax, or using lambda expressions. Then create anonymous objects (or use existing classes) containing the data you are interested in.
Best is to see it in action.
miscellaneous examples of LINQ to XML
simple sample using xquery and lambdas
sample denoting namespaces
There is tons more on msdn. Search for LINQ to XML.
Based on your sample XML and code, here's a specific example:
var element = XElement.Load(#"C:\div_kid.xml");
var shopsQuery =
from shop in element.Descendants("shop")
select new
{
Name = (string) shop.Descendants("name").FirstOrDefault(),
Company = (string) shop.Descendants("company").FirstOrDefault(),
Categories =
from category in shop.Descendants("category")
select new {
Id = category.Attribute("id").Value,
Parent = category.Attribute("parentId").Value,
Name = category.Value
},
Offers =
from offer in shop.Descendants("offer")
select new {
Price = (string) offer.Descendants("price").FirstOrDefault(),
Picture = (string) offer.Descendants("picture").FirstOrDefault()
}
};
foreach (var shop in shopsQuery){
Console.WriteLine(shop.Name);
Console.WriteLine(shop.Company);
foreach (var category in shop.Categories)
{
Console.WriteLine(category.Name);
Console.WriteLine(category.Id);
}
foreach (var offer in shop.Offers)
{
Console.WriteLine(offer.Price);
Console.WriteLine(offer.Picture);
}
}
As an extra: Here's how to deserialize the tree of categories from the flat category elements.
You need a proper class to house them, for the list of Children must have a type:
class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<Category> Children { get; set; }
public IEnumerable<Category> Descendants {
get
{
return (from child in Children
select child.Descendants).SelectMany(x => x).
Concat(new Category[] { this });
}
}
}
To create a list containing all distinct categories in the document:
var categories = (from category in element.Descendants("category")
orderby int.Parse( category.Attribute("id").Value )
select new Category()
{
Id = int.Parse(category.Attribute("id").Value),
ParentId = category.Attribute("parentId") == null ?
null as int? : int.Parse(category.Attribute("parentId").Value),
Children = new List<Category>()
}).Distinct().ToList();
Then organize them into a tree (Heavily borrowed from flat list to hierarchy):
var lookup = categories.ToLookup(cat => cat.ParentId);
foreach (var category in categories)
{
category.Children = lookup[category.Id].ToList();
}
var rootCategories = lookup[null].ToList();
To find the root which contains theCategory:
var root = (from cat in rootCategories
where cat.Descendants.Contains(theCategory)
select cat).FirstOrDefault();