Parsing the XML in C# - c#

I am getting the flight details from the expedia API, please check the link below for the XML format.
The result output contains Rateinfo then flight details as separate node and there is no relation between rateinfo and flightsegment.
Normally I load the XML into dataset and use the dataset to populate the records, but in this case there is no relation between rate and flight segment, how will I parse this XML in C#. I need to show the user the flight segments and the corresponding rates.
http://api.ean.com/ean-services/rs/air/200919/xmlinterface.jsp?cid=55505&resType=air&intfc=ws&apiKey=fc9hjrvrur9vr4y2dqa249w4&xml=<AirSessionRequest method="getAirAvailability"><AirAvailabilityQuery><originCityCode>MCI</originCityCode><destinationCityCode>CLT</destinationCityCode><departureDateTime>09/01/2011 01:00 AM</departureDateTime><returnDateTime>09/04/2011 01:00 AM</returnDateTime><fareClass>Y</fareClass><tripType>R</tripType><Passengers><adultPassengers>2</adultPassengers></Passengers><xmlResultFormat>1</xmlResultFormat><searchType>2</searchType></AirAvailabilityQuery></AirSessionRequest>

Using LINQ to XML as Braveyard mentioned above might help. You could use LINQ to break down the XML and deal with one group of rate info and flight segment at a time. If you use anonymous types you could make your own connection between one rate info and the associated flight segments, and then store that in the database.
Here is a rough example to get you going down that path:
XDocument xDoc = new XDocument();
xDoc = xDoc.Parse(responseXML); // Parse will take a string and load the XDocument with it
// You can also use Load to load from a file, StreamReader, etc.
// First, grab a collection of all the AirAvailabilityReply
var airAvailability = from x in xDoc.Descendants("AirAvailabilityReply")
select x;
// Now loop through each of the query results in the collection
foreach (var available in airAvailability)
{
// Get the rate info
var rates = from r in available.Descendants("RateInfo")
select new RateInfo {
NativeBaseFare = r.Element("nativeBaseFare").Value,
NativeTotalPrice = r.Element("NativeTotalPrice").Value,
// etc
};
// Get the flight segment info
var segments = from s in available.Descendants("FlightSegment")
select new FlightSegment {
SegmentOutgoing = s.Element("segmentOutgoing").Value,
AirlineCode = s.Element("airlineCode").Value,
// etc
};
// Now you can take RateInfo (should only be one) and the FlightSegments (should be a collection of FlightSegments) and put them into your database.
}
In the above example, I'm assuming you have two classes (RateInfo and FlightSegment) and you'll populate the properties with the corresponding values from the XML.
This is probably not the most efficient example, but hopefully it'll give you an idea of how to tackle this using LINQ to XML.

I think the basic and powerful way of parsing xml in C# would be Linq2XML.It has really easy-to-consume methods and please check the answer below:

A dataset represents relational data much easier than hierarchial data, which is what the xml represents, and that is probably the crux of your problem.
Linq2Xml is one option, or you could create a set of classes that are seriealizable and deserialize the xml into instances of the class, so then you can just iterate through the classes with foreach, or use linq on them.
I'm not saying this is the best way to go about doing what you need, but it is relatively tidy, and you don't need to deal with xml, just class instances. I tested this with the xml you provided and it worked quite well, although I do think that the Linq2Xml method might be more performant - Not 100% sure though.
Assuming your xml is in a variable called sXmlResult:
public class Test
{
public void Run()
{
//Load the xml and serialize it into instances of our classes
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(AirAvailabilityResults));
System.IO.StringReader sr = new System.IO.StringReader(sXmlResult);
AirAvailabilityResults results = (AirAvailabilityResults)ser.Deserialize(sr);
//Now we can access all the data like we would any other object.
foreach (AirAvailabilityReply reply in results.AirAvailabilityReply)
{
double dNativeBaseFare = reply.RateInfo.nativeBaseFare;
foreach (FlightSegment segment in reply.FlightSegment)
{
int iFlightNumber = segment.flightNumber;
}
}
}
}
//Create the seriealizable classes to represent the xml.
//I created these by infering the schema from the xml. These classes may need some changes, if
//they don't exactly match the actual schema that expedia uses
public class AirAvailabilityResults
{
[System.Xml.Serialization.XmlElement()]
public AirAvailabilityReply[] AirAvailabilityReply { get; set; }
[System.Xml.Serialization.XmlAttribute()]
public int size {get;set;}
[System.Xml.Serialization.XmlElement()]
public string cacheKey {get;set;}
[System.Xml.Serialization.XmlElement()]
public string cacheLocation {get;set;}
}
public class AirAvailabilityReply
{
public enum SupplierType
{
S
}
public enum TripType
{
R
}
public enum TicketType
{
E
}
[System.Xml.Serialization.XmlElement()]
public SupplierType supplierType {get;set;}
[System.Xml.Serialization.XmlElement()]
public TripType tripType {get;set;}
[System.Xml.Serialization.XmlElement()]
public TicketType ticketType {get;set;}
public RateInfo RateInfo { get; set; }
[System.Xml.Serialization.XmlElement()]
public FlightSegment[] FlightSegment {get;set;}
}
public class RateInfo
{
public double nativeBaseFare {get;set;}
public double nativeTotalPrice {get;set;}
public string nativeCurrencyCode { get; set; }
public double displayBaseFare {get;set;}
public double displayTotalPrice {get;set;}
public string displayCurrencyCode { get; set; }
}
public class FlightSegment
{
public bool segmentOutgoing {get;set;}
public string airlineCode {get;set;}
public string airline {get;set;}
public int flightNumber {get;set;}
public string originCityCode {get;set;}
public string destinationCityCode {get;set;}
public string departureDateTime {get;set;}
public string arrivalDateTime { get; set; }
public string fareClass {get;set;}
public string equipmentCode {get;set;}
public int numberOfStops {get;set;}
public string originCity {get;set;}
public string originStateProvince {get;set;}
public string originCountry {get;set;}
public string destinationCity {get;set;}
public string desintationStateProvince {get;set;}
public string destinationCountry { get; set; }
}

The API indicates that the RateInfo node corresponds to all the sibling FlightSegment nodes appended. So you dont have RateInfo for 1 FlightSegment

Related

Use multi-field List<> from Deserialized XML file to populate second single field list (Xamarin)

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();

How to ignore some records based on condition while deserialization string in to list of class

Below is my class :
public class Categories
{
public List<Categories> Subcategories { get; set; }
public List<Locations> Locations { get; set; }
}
public class Locations
{
public List<Coordinates> Coordinates { get; set; }
}
public class Coordinates
{
public int Id { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
Now Coordinates contains records like below :
[0] : Id:0
X:0
Y:0
[1] : Id:1
X:100
Y:200
[2] : Id:2
X:300
Y:400
I am receiving 1 json string which I am deserializing like below :
var data = JsonConvert.DeserializeObject<List<Categories>>(json);
Now what I want to do is I want to ignore record from coordinates whose Id value is 0 while deserializing json in List of categories.
I know I can loop on to this data and then remove records from Coordinates where Id == 0 but I am trying to avoid this loop and would like to know that is there any way to do achieve this while deserialization of string in to class?
If you are interested in knowing another way, then yes there is. You can inherit from JsonConvert class and provide own implementation for serialization and deserialization by overriding WriteJson and ReadJson methods. It is nothing difficult so I leave the implementation to you.
Sample: http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
However, if it's just about filtering record for specific ID then KISS. Go for filtering once you have deserialized JSON data. LINQ has a one liner solution for that. No looping, please :)

Avoiding repetition when writing different objects with identical data structures

In my current system I've got two EF objects with near-identical sets of properties but which can't for various reasons, be unified or joined to share some of the same structure. It's effectively personal details, so it looks a bit like this:
public partial class Person
{
public int PersonID { get; set; }
public string Title { get; set; }
public string Surname { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
// .. etc
}
public partial class Member
{
public int MemberID { get; set; }
public string Title { get; set; }
public string Surname { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
// .. etc
}
Eventually a mixture of these two object types have to be written to the same file, like this:
foreach (Person p in People)
{
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.Append("\"");
lineBuilder.Append(p.PersonID.ToString());
lineBuilder.Append("\",\"");
lineBuilder.Append(p.Title);
lineBuilder.Append("\",\"");
lineBuilder.Append(p.Forename);
lineBuilder.Append("\",\"");
// etc...
}
foreach (Member m in Members)
{
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.Append("\"");
lineBuilder.Append(m.MemberID.ToString());
lineBuilder.Append("\",\"");
lineBuilder.Append(m.Title);
lineBuilder.Append("\",\"");
lineBuilder.Append(m.Forename);
lineBuilder.Append("\",\"");
// etc...
}
This is obviously A Bad Thing - not only does it repeat code, but it's reliant on both loops maintaining the same number of columns in the output which might easily be overlooked if the code is changed.
Short of creating an intermediary object and mapping the fields on to it - which is really just shifting the problem elsewhere - is there a way to approach this that allows me to get this down to a single loop?
Put an interface on the two entities and let your logic work against the interface instead of the concrete classes
Assuming you are stuck with those two entities since you are using DB first EF, three suggestions I can think of:
Use AutoMapper to map both these objects to a third class. In the third class overwrite ToString(). If you include the AutoMapper unit test to validate mappings you never have to worry about changes - AutoMapper will pick it up and fail the test.
Refactor the DB to a polymorphic Person table with a Person type field and then you have just one Person entity with a person type field.
Use code first EF and have a base person class, or interface.
You're doing two things here. You're wrapping fields in quotes, and you're joining a series of strings together with commas. Simply write a method that does that for any number of arbitrary strings:
public static string BuildLine(IEnumerable<string> fields)
{
return string.Join(",", fields.Select(WrapInQuotes));
}
private static string WrapInQuotes(string rawData)
{
return string.Format("\"{0}\"", rawData);
}
Once you have this you can now have each of your classes simply provide a collection of their fields, without being responsible for formatting them for output.
Note that it looks like you're writing out a CSV file here. My first advice is don't. Just use an existing CSV writer that will be able to handle all of the character escaping and so on for you so that you don't have to. There are some things that you haven't accounted for so far including fields that have line breaks or quotes in them, or the fact that you really shouldn't be quote escaping every single field, but rather only those that contain characters requiring quote escaping (namely line breaks, if you want to support multi-line fields, or commas).
Create an interface (or abstract class) for it.
public interface IDetails
{
string Title;
string Surname;
string AddressLine1;
string AddressLine2;
}
public partial class Member : IDetails
{
//etc
}
public partial class Person : IDetails
{
//etc
}
you can use interface,
public interface ISocialHuman{
public int PersonID { get; set; }
public string Title { get; set; }
public string Surname { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
}
public static void writeHumanSomewhere(ISocialHuman){
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.Append("\"");
lineBuilder.Append(p.PersonID);
lineBuilder.Append("\",\"");
lineBuilder.Append(p.Title);
lineBuilder.Append("\",\"");
lineBuilder.Append(p.Forename);
lineBuilder.Append("\",\"");
}
But you should change the ID name.

How to read stackoverflow rss feed using linq to xml

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?)

LINQ to XML - Extracting values inside nested namespaces into an object

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};

Categories

Resources