I am trying read rss feed of stack overflow using Linq to xml. I am unable to get the entry nodes, as it is returning empty list. This I've tried so far, can any one point out what i am doing wrong here?
Here I am binding to the grid view:
private void StackoverflowFeedList()
{
grdFeedView.DataSource = StackoverflowUtils.GetStackOverflowFeeds();
grdFeedView.DataBind();
}
This is the method which will get all feeds:
public static IEnumerable<StackOverflowFeedItems> GetStackOverflowFeeds ()
{
XNamespace Snmp = "http://www.w3.org/2005/Atom";
XDocument RssFeed = XDocument.Load(#"http://stackoverflow.com/feeds");
var posts = from item in RssFeed.Descendants("entry")
select new StackOverflowFeedItems
{
QuestionID = item.Element(Snmp +"id").Value,
QuestionTitle = item.Element(Snmp +"title").Value,
AuthorName = item.Element(Snmp +"name").Value,
CategoryTag = (from category in item.Elements(Snmp +"category")
orderby category
select category.Value).ToList(),
CreatedDate = DateTime.Parse(item.Element(Snmp +"published").Value),
QuestionSummary = item.Element(Snmp +"summary").Value
};
return posts.ToList();
}
And this is the class I am using for binding:
public class StackOverflowFeedItems
{
public string QuestionID { get; set; }
public string QuestionTitle { get; set; }
public IEnumerable<string> CategoryTag { get; set; }
public string AuthorName { get; set; }
public DateTime CreatedDate { get; set; }
public string QuestionSummary { get; set; }
}
You're not using the namespace variable you've declared. Try using
RssFeed.Descendants(Snmp + "entry")
(And likewise for all other places where you're referring to particular names.)
I'm not saying that's necessarily all of what you need to fix, but it's the most obvious problem. You should also consider using the explicit conversions of XElement and XAttribute instead of the Value property, e.g.
CreatedDate = (DateTime) item.Element(Snmp +"published")
I'd also encourage you to pay more attention to indentation, and use pascalCase consistently when naming local variables. (Quite why the namespace variable is called Snmp is another oddity... cut and paste?)
Related
I have and so far failed at implementing 2 approaches to parse an xml tree and cast it, along with its children, into objects. I have tried object serialization as explain here and I have used Linq as explained by the accepted answer here.
With the first approach (deserialization), it works up until the List<ExtensionItem> attribute of IndividualOrEntityValidationExtensions not getting its values assigned (i.e it remains null).
With the second approach (LINQ), I get this error pertaining to the entire OutputFilePaths =... block.
/Users/g-wizkiz/Projects/XmlParser/XmlParser/Program.cs(68,68): Error CS0266: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<XmlParser.models.OutputFilePaths>' to 'XmlParser.models.OutputFilePaths'. An explicit conversion exists (are you missing a cast?) (CS0266) (XmlParser)
I'm happy to go with whichever works, though I do feel like LINQ is a more elegant approach.
I will show my XML and class structure, followed by the respective code blocks.
XML
<?xml version="1.0" encoding="UTF-8" ?>
<ParameterList>
<Parameters>
<NumberOfThreads>10</NumberOfThreads>
<InputFilePath>C:\Input.dat</InputFilePath>
<OutputFilePaths>
<NameFile>name.txt</NameFile>
<ValidationFile>validation.txt</ValidationFile>
<AuditLog>audit.txt</AuditLog>
</OutputFilePaths>
<DictionaryExtensions>
<IndividualOrEntityValidationExtensions>
<ExtensionItem>
<Type>dictType1</Type>
<Path>dictPath1</Path>
</ExtensionItem>
<ExtensionItem>
<Type>dictType2</Type>
<Path>dictPat2</Path>
</ExtensionItem>
</IndividualOrEntityValidationExtensions>
</DictionaryExtensions>
</Parameters>
</ParameterList>
Classes
[XmlRoot("Parameters")]
public class Parameters
{
public int NumberOfThreads { get; set; }
public string InputFilePath { get; set; }
public OutputFilePaths outputFilePaths { get; set; }
public DictionaryExtensions DictionaryExtensions { get; set; }
}
public class OutputFilePaths
{
public string NameFile { get; set; }
public string ValidationFile { get; set; }
public string AuditLog { get; set; }
}
public class DictionaryExtensions
{
[XmlElement("IndividualOrEntityValidationExtensions")]
public IndividualOrEntityValidationExtension IndividualOrEntityValidationExtensions { get; set; }
}
public class IndividualOrEntityValidationExtension
{
[XmlArrayItem("ExtensionItem")]
public List<ExtensionItem> ExtensionItem { get; set; }
}
public class ExtensionItem
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlAttribute("Path")]
public string Path { get; set; }
}
Object deserialization approach
string xmlString = System.IO.File.ReadAllText(#"/Users/g-wizkiz/Projects/XmlParser/XmlParser/parameters.xml");
XmlSerializer serializer = new XmlSerializer(typeof(List<Parameters>), new XmlRootAttribute("ParameterList"));
StringReader stringReader = new StringReader(xmlString);
List<Parameters> parameters = (List<Parameters>)serializer.Deserialize(stringReader)
LINQ approach
XDocument doc = XDocument.Parse(xmlString);
IEnumerable<Parameters> result = from c in doc.Descendants("Parameters")
select new Parameters()
{
NumberOfThreads = (int)c.Attribute("NumberOfThreads"),
InputFilePath = (string)c.Attribute("InputFilePath"),
outputFilePaths = from f in c.Descendants("OutputFilePaths")
select new OutputFilePaths()
{
ValidationFile = (string)f.Attribute("ValidationFile"),
AuditLog = (string)f.Attribute("AuditLog"),
NameFile = (string)f.Attribute("NameFile")
}
};
Cheers!
Testing locally, this works fine as the only change:
public class IndividualOrEntityValidationExtension
{
[XmlElement("ExtensionItem")]
public List<ExtensionItem> ExtensionItem { get; set; }
}
However, you can remove a level in the hierarchy if you prefer - throwing away the IndividualOrEntityValidationExtension type completely:
public class DictionaryExtensions
{
[XmlArray("IndividualOrEntityValidationExtensions")]
[XmlArrayItem("ExtensionItem")]
public List<ExtensionItem> ExtensionItems { get; set; }
}
I'm thinking the problem is related to the Parameter class where the OutputFilePaths is a single entity and not an IEnumerable<OutputFilePaths> or if you are expecting only one outputfilepath then select the .FirstOrDefault() in your linq query (be prepared for null values).
I fixed your linq. The nodes are elements not attributes.
XDocument doc = XDocument.Parse(xmlString);
IEnumerable<Parameters> result = (from c in doc.Descendants("Parameters")
select new Parameters()
{
NumberOfThreads = (int)c.Element("NumberOfThreads"),
InputFilePath = (string)c.Element("InputFilePath"),
outputFilePaths = (from f in c.Descendants("OutputFilePaths")
select new OutputFilePaths()
{
ValidationFile = (string)f.Element("ValidationFile"),
AuditLog = (string)f.Element("AuditLog"),
NameFile = (string)f.Element("NameFile")
}).FirstOrDefault()
}).ToList();
This has to be so simple... in T-SQL it would take me a second.
Before I get duplicate flags... I have been trying some similar problems posted and I have been on this for hours, tinkering with various replies involving JTokens, changing to IEnumerable, deserializing one field only to name a few. I keep getting errors such as:
xamarin cannot implicitly convert type
'System.Collections.Generic.List to 'System.Colletions.List
I have deserialized an xml file into a list like so:
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;
Stream stream = assembly.GetManifestResourceStream("papapp.WineCatInfo.xml");
List<WineCategoryInfo> winecategoryinfos;
using (var reader = new StreamReader(stream))
{
var serializer = new XmlSerializer(typeof(List<WineCategoryInfo>));
winecategoryinfos = (List<WineCategoryInfo>)serializer.Deserialize(reader);
}
#endregion
return winecategoryinfos;
The list it populates is defined like so:
public class WineCategoryInfo
{
public string WineTypeNo { get; set; }
public string WineFamily { get; set; }
public string MainStyle { get; set; }
public string StyleType { get; set; }
public string LengthCharacteristic { get; set; }
public string RegionCommonAppelation { get; set; }
}
I would like a query on the deserialized date to place all the values of one particular field from the above list into a second list, defined like so:
public class WineFamilies
{
public string WineFamily { get; set; }
}
Can someone explain to me how to achieve this?
Many thanks.
use LINQ
using System.Linq;
var family = (from x in winecategoryinfos select x.WineFamily).ToList<string>();
or to eliminate duplicates
var family = (from x in winecategoryinfos select x.WineFamily).Distinct().ToList<string>();
if you want to use your WineFamilies class instead of string, try
var family = (from x in winecategoryinfos select new WineFamilies() { WineFamily = x.WineFamily }).ToList();
I have a blog export package which exports blog content in Umbraco to XML.
Now I want to export comment data, the comments section is set as a childNode on the NewsItem node, how can I use this format to grab the data from the childNode into the list?
Here is my code:
public List<BlogPosts> getPostList()
{
var contentType = ApplicationContext.Current.Services.ContentTypeService
.GetContentType("umbNewsItem");
var nodes = ApplicationContext.Current.Services.ContentService
.GetContentOfContentType(contentType.Id).Select(content => new Node(content.Id));
return nodes.Select(node => new BlogPosts()
{
Title = node.GetProperty("title").ToNullSafeString(),
BodyText = node.GetProperty("bodyText").ToNullSafeString(),
PublishDate = node.GetProperty("publishDate").ToNullSafeString(),
Author = node.GetProperty("author").ToNullSafeString(),
Image = node.GetProperty("image").ToNullSafeString(),
//This is where I want to grab the blog comments content
Comments = node.ChildrenAsList.Add("comments")
}).ToList();
}
My first attempt with this, I get an error on the .Add("comments") line which reads:
The best overloaded method match for 'System.Collections.Generic.List<umbraco.interfaces.INode>.Add(umbraco.interfaces.INode)' has some invalid arguments
the next thing I tried was this:
Comments = node.ChildrenAsList<BlogComment>.Add("comments").ToList()
which returns the following error:
The property 'umbraco.NodeFactory.Node.ChildrenAsList' cannot be used with type arguments
I have also tried this:
Comments = node.ChildrenAsList.Add("comments").ToList()
which returned this error:
The best overloaded method match for 'System.Collections.Generic.List<umbraco.interfaces.INode>.Add(umbraco.interfaces.INode)' has some invalid arguments
This is my BlogPosts model:
public class BlogPosts
{
public string Title { get; set; }
public string BodyText { get; set; }
public string PublishDate { get; set; }
public string Author { get; set; }
public string Image { get; set; }
public List<BlogComment> Comments { get; set; }
}
public class BlogComment
{
public string Comment { get; set; }
public string CommentDate { get; set; }
}
This is an example of the Umbraco backoffice page:
Image
I've searched throughout stackoverflow and google for anything which refers to calling data from a childNode into a list but the list type here is INode, when using this:
Comments = node.ChildrenAsList
it returns this error:
Cannot implicitly convert type 'System.Collections.Generic.List<umbraco.interfaces.INode>' to 'System.Collections.Generic.List<UmbracoBlogsExportPackage.Models.BlogComment>'
Okay then :-)
First of all, .Add() tries to add something to a collection, so that
won't work here.
Second, I think selecting Content as Nodes is a bit backwards, so I
would try not to do that.
Third, IEnumerable have a Cast() method that I think might work
here. I can't really test it, though.
Again, this is very untested, but maybe try something like this? Obviously I don't know the Comment DocType alias, so remember to change that bit :-)
public List<BlogPosts> getPostList()
{
var contentType = UmbracoContext.Current.Application.Services.ContentTypeService
.GetContentType("umbNewsItem");
var contentService = UmbracoContext.Current.Application.Services.ContentService;
var nodes = contentService.GetContentOfContentType(contentType.Id);
return nodes.Select(node => new BlogPosts()
{
Title = node.GetValue("title").ToNullSafeString(),
BodyText = node.GetValue("bodyText").ToNullSafeString(),
PublishDate = node.GetValue("publishDate").ToNullSafeString(),
Author = node.GetValue("author").ToNullSafeString(),
Image = node.GetValue("image").ToNullSafeString(),
//This is where I want to grab the blog comments content
Comments = contentService.GetChildren(node.Id).Where(x => x.ContentType.Alias == "Comment").Cast<BlogComment>().ToList()
}).ToList();
}
I'm having a lot of trouble parsing an XML document into my custom classes. I've tried to read what I can find on the web and on here, but I'm still not getting anywhere. I'm working on a real estate app, and am trying to model a basic property where you have:
1 property
1 property can have multiple buildings
Each building can have multiple tenants.
I decided to try to store the data in an xml document, and I made an example as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Property>
<Name>Grove Center</Name>
<Building>
<Name>Building1</Name>
<Tenant>
<Name>Tenant1</Name>
<SquareFeet>2300</SquareFeet>
<Rent>34000</Rent>
</Tenant>
<Tenant>
<Name>Tenant2</Name>
<SquareFeet>3100</SquareFeet>
<Rent>42000</Rent>
</Tenant>
<Tenant>
<Name>Tenant3</Name>
<SquareFeet>1700</SquareFeet>
<Rent>29000</Rent>
</Tenant>
</Building>
<Building>
<Name>Building2</Name>
<Tenant>
<Name>Tenant1</Name>
<SquareFeet>6150</SquareFeet>
<Rent>80000</Rent>
</Tenant>
<Tenant>
<Name>Tenant2</Name>
<SquareFeet>4763</SquareFeet>
<Rent>60000</Rent>
</Tenant>
</Building>
</Property>
Actually my first question is if this format is even correct.. I saw some xml examples where they added an extra tag such as <buildings> before they started listing out the individual <Building> tags for each building. Is that necessary? The W3C examples I saw didn't do it that way.. but this post on stackexchange was pretty close to what im doing: Parsing XML with Linq with multiple descendants
Here is the code for my classes in C#:
public class Property
{
public string Name { get; set; }
public List<Building> Buildings = new List<Building>();
}
public class Building
{
public string Name { get; set; }
public List<Tenant> Tenants = new List<Tenant>();
}
public class Tenant
{
public string Name { get; set; }
public int SF { get; set; }
public decimal Rent { get; set; }
}
I'm not sure if using the new keyword on my lists right in the class definition is good practice.. but I was getting errors trying to add a building or tenant to the list later on in my program so I didn't know what else to do. Right now I'm not much further in my main code than:
Property p = new Property();
XDocument doc = XDocument.Load(#"C:\Users\SampleUser\Desktop\sample-property.xml");
Any help is appreciated, thanks
Following query will give you the correct result:-
Property p = new Property
{
Name = (string)doc.Root.Element("Name"),
Buildings = doc.Root.Elements("Building")
.Select(x => new Building
{
Name = (string)x.Element("Name"),
Tenants = x.Elements("Tenant")
.Select(t => new Tenant
{
Name = (string)t.Element("Name"),
SF = (int)t.Element("SquareFeet"),
Rent = (decimal)t.Element("Rent")
}).ToList()
}).ToList()
};
Theres a few things you might want to change.
The property names must match the xml tags, or you have to specify the mapping manually. In your example code, Buildings and Tenants are declared as fields, you should change it to properties. If you want, you can then initialize them to empty list in the constructors:
public class Property
{
public string Name { get; set; }
[XmlElement("Building")]
public List<Building> Buildings { get; set; }
public Property()
{
Buildings = new List<Building>();
}
}
public class Building
{
public string Name { get; set; }
[XmlElement("Tenant")]
public List<Tenant> Tenants { get; set; }
public Building()
{
Tenants = new List<Tenant>();
}
}
public class Tenant
{
public string Name { get; set; }
[XmlAttribute("SquareFeet")]
public int SF { get; set; }
public decimal Rent { get; set; }
}
Further, I would recommend deserializing the file rather than using linq. Consider these helper methods:
public static class XmlHelper
{
public static T DeserializeFromXmlString<T>(string xml)
{
var xmlSerializer = new XmlSerializer(typeof (T));
using (var stringReader = new StringReader(xml))
{
return (T) xmlSerializer.Deserialize(stringReader);
}
}
public static T DeserializeFromXmlFile<T>(string filename) where T : new()
{
return DeserializeFromXmlString<T>(File.ReadAllText(filename));
}
}
Deserialization is then easy:
var listOfProperties = XmlHelper.DeserializeFromXmlFile<Property>(#"C:\Users\SampleUser\Desktop\sample-property.xml");
Intializing your public fields with empty lists is perfectly fine and good practice to avoid the errors you got. If you do not initialize them, they are null, hence the errors.
You could use properties instead of fields for your lists however.
Starting with C# 6 you can use simplified auto-property assignment:
public List<Building> Buildings {get;set;} = new List<Building>();
For C# < 6 you can use auto properties and initialize the property within the constructor or use a property with backing field.
//Auto property with assignment in constructor
public class Property
{
public string Name { get; set; }
public List<Building> Buildings {get;set;};
public Property(){
Buildings = new List<Building>();
}
}
//Property with backing field
public class Property
{
private List<Building> _buildings = new List<Building>();
public string Name { get; set; }
public List<Building> Buildings {get {return _buildings;} set {_buildings = value;}};
}
For reading XML and creating the object graph, you can use LINQ in conjuction with object initializers.
Func<IEnumerable<XElement>, IEnumerable<Tenant>> getTenants = elements => {
return elements.Select (e => new Tenant {
Name = e.Element("Name").Value,
Rent = decimal.Parse(e.Element("Rent").Value),
SF = int.Parse(e.Element("SquareFeet").Value)
});
};
Func<IEnumerable<XElement>, IEnumerable<Building>> getBuildings = elements => {
return elements.Select (e => new Building{
Name = e.Element("Name").Value,
Tenants = getTenants(e.Elements("Tenant")).ToList()
});
};
//xdoc is your parsed XML document
//e.g. var xdoc = XDdocument.Parse("xml contents here");
var property = new Property{
Name = xdoc.Root.Element("Name").Value,
Buildings = getBuildings(xdoc.Root.Elements("Building")).ToList()
};
I'd like to use LINQ to XML to extract values from an XML doc that has nested namespaces.
My question had partially been answered by another SO user: LINQ to XML w/ nested namespaces
I am still having trouble trying to figure out how to select the values into an object's properties.
Here are the resources for this problem:
Sample XML File Link
Contents of above link:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns1:Envelope xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns3="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-4">
<ns1:Header>
<ns3:TransactionID ns1:mustUnderstand="1">XXXX-XXXX-XXXX-XXXX</ns3:TransactionID>
</ns1:Header>
<ns1:Body>
<ns3:DeliverReq>
<ns3:MM7Version>6.8.0</ns3:MM7Version>
<ns3:LinkedID>LINKED-ID-IS-HERE</ns3:LinkedID>
<ns3:Sender>
<ns3:Number>3025551212</ns3:Number>
</ns3:Sender>
<ns3:Recipients>
<ns3:To>
<ns3:Number displayOnly="false">11111</ns3:Number>
</ns3:To>
</ns3:Recipients>
<ns3:TimeStamp>2011-04-25T10:28:40.000Z</ns3:TimeStamp>
<ns3:UACapabilities UAProf="motok1c"/>
<ns3:Content allowAdaptations="true" href="cid:default.cid"/>
</ns3:DeliverReq>
</ns1:Body>
</ns1:Envelope>
Object to be filled from this XML:
public class TestClass
{
public string TransactionId { get; set; }
public string MessageType { get; set; }
public string Mm7Version { get; set; }
public string VaspId { get; set; }
public string VasId { get; set; }
public string Sender { get; set; }
public string Recipients { get; set; }
public string LinkedId { get; set; }
public string TimeStamp { get; set; }
public string Priority { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public string UaCapabilities { get; set; }
// not all properties appear in every XML message received
}
The SO example helped me understand the need to add a namespace (XNamespace), but I am falling short when trying to fill the object using a LINQ statement.
I think I need to do something like:
// (don't know the "from" in this case - decendants?
// which XML key / node?)
select new TestClass
{
TransactionId = [LINQ to select the "TransactionID" value that
appears under the Header key]
Mm7Version = [LINQ to select the "MM7Version" value, under the
DeliverReq key]
.....
}
What is the shape of the LINQ to be able to select values into properties of an object when you have XML like posted above?
I care about the data in 2 portions of the XML: The TransactionID value in the header, and the values that are under the DeliverReq. It is odd to be that they are spread out and it is more confusing than simply selecting the values strictly from the DeliverReq key. If I saw the FROM portion, and 2 to 3 of the properties filled with values from the XML then I would be able to pick it up from there.
Please let me know if you need any clarifications. My main problems are with the "FROM" portion of the LINQ and how to deal with the fact that TransactionId is outside of the DeliverReq tag. I think I can handle the other cases, such as the nesting on the values under the Sender and Recipients tags.
Since there is only properties for one of your TestClass instances in your XML, and the values for those are all over the place it makes sense to select them manually and then create the instance:
XDocument doc = XDocument.Load(#"test.xml");
XNamespace ns3 = "http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-4";
string transactionId = doc.Descendants(ns3 + "TransactionID").Single().Value;
string mm7Version = doc.Descendants(ns3 + "MM7Version").Single().Value;
//...select the other elements
TestClass testClass = new TestClass() { TransactionId = transactionId,
Mm7Version = mm7Version};