I am trying to parse the following XML files in to a list. Unfortunately it returns only one element
Sample XML
<Titles>
<Book Title ="Love Story" Author= "Erich Segal" Year = "1999"/>
<Book Title ="Code Complete" Author= "Steve McConnel" Year = "2004"/>
<Book Title ="Rework" Author = "Jaso Fried" Year = "2010"/>
<Book Title ="Delivering Happiness" Author= "Tony Hseigh" Year = "2011"/>
</Titles>
C# Code
public class BookInfo
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
}
XDocument xmlDoc = XDocument.Load(strXMLPath);
var b = from device in xmlDoc.Descendants("Titles")
select new BookInfo
{
Title = device.Element("Book").Attribute("Title").Value,
Author = device.Element("Book").Attribute("Author").Value,
Year = int.Parse(device.Element("Book").Attribute("Year").Value)
};
books = b.ToList();
I suspect you actually want to be finding descendants called "Book" rather than "Titles":
XDocument xmlDoc = XDocument.Load(strXMLPath);
var b = from book in xmlDoc.Descendants("Book")
select new BookInfo
{
Title = (string) book.Attribute("Title"),
Author = (string) book.Attribute("Author"),
Year = (int) book.Attribute("Year")
};
var books = b.ToList();
Or in non-query expression syntax:
XDocument xmlDoc = XDocument.Load(strXMLPath);
var books = xmlDoc.Descendants("Book")
.Select(book => new BookInfo
{
Title = (string) book.Attribute("Title"),
Author = (string) book.Attribute("Author"),
Year = (int) book.Attribute("Year")
})
.ToList();
EDIT: If you want all elements descending from Titles (e.g. to exclude "Book" elements from elsewhere), you'd want:
XDocument xmlDoc = XDocument.Load(strXMLPath);
var books = xmlDoc.Descendants("Titles")
.Descendants("Book")
.Select(book => /* same as before */)
Related
I have the following XML. What is the best way to get the data?
<?xml version='1.0' encoding='UTF-8'?>
<Root>
<EmployeeDataRoot>
<EmployeeData>
<Employee_id>123456</Employee_id>
<Employee_Status>A</Employee_Status>
<Business_Unit>EN00</Business_Unit>
<Cost_Center>0904/1992</Cost_Center>
<Work_Location>DFW</Work_Location>
<Location>DFW-HDQ1</Location>
<Job_Category>0003</Job_Category>
<Last_Name>John</Last_Name>
<First_Name>Doe</First_Name>
<Middle_Name />
<Preferred_Name />
<Position_Title>Programmer/Analyst</Position_Title>
<Legal_Entity>EN00</Legal_Entity>
<Department_Unit>IT HR & Employee Technology</Department_Unit>
<Run_Date>2016-12-12</Run_Date>
</EmployeeData>
</EmployeeDataRoot>
<Footer_No_of_Records>
<Records>1</Records>
</Footer_No_of_Records>
</Root>
After looking at some examples online, I tried these two iterations but get an error
object not set to an instance of an object
I looked over the properties of my Employee class as well as the Nodes for any misspellings and didn't see any. I think the error is that I'm not querying the XML properly.
var xDoc = XDocument.Load(file.FullName);
listEmployee =
(from e in xDoc.Descendants("EmployeeData")
select new Employee
{
EmployeeID = e.Element("Employee_ID").Value,
EmployeeStatus = e.Element("Employee_Status").Value,
BusinessUnit = e.Element("Business_Unit").Value,
CostCenter = e.Element("Cost_Center").Value,
WorkLocation = e.Element("Work_Location").Value,
Location = e.Element("Location").Value,
JobCategory = e.Element("Job_Category").Value,
FirstName = e.Element("First_Name").Value,
LastName = e.Element("Last_Name").Value,
LegalEntity = e.Element("Legal_Entity").Value
}
).ToList();
and I also tried
listEmployee =
(from e in xDoc.Element("Root").Elements("EmployeeDataRoot/EmployeeData")
select new Employee
{
EmployeeID = e.Element("Employee_ID").Value,
EmployeeStatus = e.Element("Employee_Status").Value,
BusinessUnit = e.Element("Business_Unit").Value,
CostCenter = e.Element("Cost_Center").Value,
WorkLocation = e.Element("Work_Location").Value,
Location = e.Element("Location").Value,
JobCategory = e.Element("Job_Category").Value,
FirstName = e.Element("First_Name").Value,
LastName = e.Element("Last_Name").Value,
LegalEntity = e.Element("Legal_Entity").Value
}
).ToList();
Your attempt, ist right, but you write "Employee_ID" wrong. Try this out:
var xDoc = XDocument.Load(file.FullName);
listEmployee =
(from e in xDoc.Descendants("EmployeeData")
select new Employee
{
EmployeeID = e.Element("Employee_id").Value,
EmployeeStatus = e.Element("Employee_Status").Value,
BusinessUnit = e.Element("Business_Unit").Value,
CostCenter = e.Element("Cost_Center").Value,
WorkLocation = e.Element("Work_Location").Value,
Location = e.Element("Location").Value,
JobCategory = e.Element("Job_Category").Value,
FirstName = e.Element("First_Name").Value,
LastName = e.Element("Last_Name").Value,
LegalEntity = e.Element("Legal_Entity").Value
}
).ToList();
I'm trying to parse xml but certain elements aren't returning anything. the first block returns good data but the artist nodes and venue nodes all return null. im not sure what im doing wrong.
XDocument xml = XDocument.Parse(data);
var events = xml.Descendants("events");
foreach(var e in events)
{
foreach(var attribute in e.Descendants("event"))
{
string id = (string)attribute.Element("id");
string Title = (string)attribute.Element("title");
DateTime date = DateTime.Parse(((string)attribute.Element("datetime")), null, DateTimeStyles.RoundtripKind);
string TicketUrl = (string)attribute.Element("ticket_url");
TicketType TicketType = (TicketType)Enum.Parse(typeof(TicketType), (string)attribute.Element("ticket_type"));
TicketAvailability TicketAvailability = (TicketAvailability)Enum.Parse(typeof(TicketAvailability), (string)attribute.Element("ticket_status"));
DateTime TicketSaleDate = DateTime.Parse(((string)attribute.Element("on_sale_datetime")), null, DateTimeStyles.RoundtripKind);
DateTime LastAvailabilityCheck = DateTime.Now;
string FacebookRsvpUrl = (string)attribute.Element("facebook_rsvp_url");
string Description = (string)attribute.Element("description");
List<Artist> artistData = new List<Artist>();
Venue venue = new Venue();
foreach (var artistNode in attribute.Descendants("artist"))
{
var artistName = (string)artistNode.Attribute("name");
var junk = (string)artistNode.Attribute("mbid");
var ImageUrl = (string)artistNode.Attribute("image_url");
var ThumbnailUrl = (string)artistNode.Attribute("thumb_url");
var FaceBookUrl = (string)artistNode.Attribute("facebook_tour_dates_url");
artistData.Add(new Artist(artistName, ThumbnailUrl, ImageUrl, FaceBookUrl));
}
foreach (var venueNode in attribute.Descendants("venue"))
{
var VenueName = (string)venueNode.Element("name");
var City = (string)attribute.Element("city");
var Region = (string)venueNode.Attribute("region");
var Country = (string)venueNode.Attribute("country");
var GpsLocation = (string)venueNode.Attribute("latitude") + ", " + (string)attribute.Attribute("longitude");
venue = new Venue(VenueName, City, Region, Country, GpsLocation);
}
concerts.Add(new Concert(id, artistData, venue, date, TicketSaleDate, Description, FacebookRsvpUrl, Title, TicketUrl, TicketType, TicketAvailability, LastAvailabilityCheck));
}
}
sample data:
<?xml version="1.0" encoding="UTF-8"?>
<events>
<event>
<id>9203898</id>
<title>Fall Out Boy # DTE Energy Music Theatre in Clarkston, MI</title>
<datetime>2015-07-10T19:00:00</datetime>
<formatted_datetime>Friday, July 10, 2015 at 7:00PM</formatted_datetime>
<formatted_location>Clarkston, MI</formatted_location>
<ticket_url>http://www.bandsintown.com/event/9203898/buy_tickets?app_id=BandJunkieForWindows&artist=Fall+Out+Boy</ticket_url>
<ticket_type>Tickets</ticket_type>
<ticket_status>available</ticket_status>
<on_sale_datetime>2015-01-23T10:00:00</on_sale_datetime>
<facebook_rsvp_url>http://www.bandsintown.com/event/9203898?app_id=BandJunkieForWindows&artist=Fall+Out+Boy&came_from=67</facebook_rsvp_url>
<description></description>
<artists>
<artist>
<name>Fall Out Boy</name>
<mbid>516cef4d-0718-4007-9939-f9b38af3f784</mbid>
<image_url>http://www.bandsintown.com/FallOutBoy/photo/medium.jpg</image_url>
<thumb_url>http://www.bandsintown.com/FallOutBoy/photo/small.jpg</thumb_url>
<facebook_tour_dates_url>http://bnds.in/1n0W0qQ</facebook_tour_dates_url>
</artist>
</artists>
<venue>
<name>name</name>
<city>place</city>
<region>state</region>
<country>United States</country>
<latitude>142.748783</latitude>
<longitude>-183.380201</longitude>
</venue>
</event>
</events>
You're using Attribute, but your XML contains elements. Use Element instead...
foreach (var artistNode in attribute.Descendants("artist"))
{
var artistName = (string)artistNode.Element("name");
// etc...
Also as a sidenote, you can cast XElement and XAttribute to DateTime instead of calling DateTime.Parse:
DateTime TicketSaleDate = DateTime.Parse(((string)attribute.Element("on_sale_datetime")), null, DateTimeStyles.RoundtripKind);
can be
DateTime TicketSaleDate = (DateTime)attribute.Element("on_sale_datetime");
The sample below is parsing an XML document then looping through the members and storing them in a list of objects (The data ultimately ends up in an SQL database):
public static void Parse(XDocument xml)
{
XNamespace ns = "http://somenamespace.com/ns";
var Locations =
from Continents in xml.Descendants(ns + "Continent")
from Countries in Continents.Elements(ns + "Country")
select new
{
Continent1 = (string) Continents.Element(ns + "Europe"),
Country1 = (string) Countries.Element(ns + "United_Kingdom"),
Cities = from Cities in Countries.Elements(ns + "City")
select new
{
City1 = (string) Cities.Element(ns + "London")
}
};
List<Location> locationColl = new List<Location>();
loc_Entity_FrameworkContainer context = new loc_Entity_FrameworkContainer();
var i = 0;
foreach (var location in Locations)
{
Location l = new Location();
locationColl.Add(l);
locationColl[i].Continent = (string) location.Continent1;
locationColl[i].Country = (string) location.Country1;
locationColl[i].City = (string) location.City1; // Can't access "City1"
context.Location.Add(locationColl[i]);
i++;
}
context.SaveChanges();
}
The statement: locationColl[i].City = (string)location.City1;
doesn't find "City1". (This is the issue, I can't access all the members from "Locations" in one loop)
Location Class:
namespace locationProject
{
using System;
using System.Collections.Generic;
public partial class Location
{
public string Continent { get; set; }
public string Country { get; set; }
public string City { get; set; }
}
}
XML Example:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:ns="http://somenamespace.com/ns">
<ns:Continent>
<ns:Europe>21c99a56-4b3d-4571-802a-76cdb6b81a01</ns:Europe>
<ns:Country>
<ns:United_Kingdom>eb2e9eec-dc3b-4636-bcf5-dba0024e62f3</ns:United_Kingdom>
<ns:City>
<ns:London>109b48ec-d829-4a87-b200-4dc9a94db48c</ns:London>
</ns:City>
</ns:Country>
</ns:Continent>
<ns:Continent>
<ns:Europe>a11ed925-dc0d-4dfd-b1c2-52eb697ad689</ns:Europe>
<ns:Country>
<ns:United_Kingdom>a61d02ef-7b80-4390-926a-49c6d9af9634</ns:United_Kingdom>
<ns:City>
<ns:London>dbb9c5cc-b08f-4223-b32c-acb4ed9ce97c</ns:London>
</ns:City>
</ns:Country>
</ns:Continent>
</feed>
I'm trying to find a way of looping through all the elements (Continent1, Country1, City1) that doesn't involve multiple loops and doesn't break the nested structure of the LINQ statements.
There are questions on here similar to this one, but I haven't found one I understand well enough to integrate with my code.
Thanks a lot!
Your anonymous type contained in the Locations list has a .Cities property that contains a City1 member:
Cities = from Cities in Countries.Elements(ns + "City")
select new
{
City1 = (string) Cities.Element(ns + "London")
}
Try this:
var Locations =
from Continents in xml.Descendants(ns + "Continent")
from Countries in Continents.Elements(ns + "Country")
from Cities in Countries.Elements(ns + "City")
select new
{
Continent1 = (string) Continents.Element(ns + "Europe"),
Country1 = (string) Countries.Element(ns + "United Kingdom"),
City1 = (string) Cities.Element(ns + "London")
};
<?xml version="1.0" standalone="yes"?>
<CompanyInfo>
<Employee name="Jon" deptId="123">
<Region name="West">
<Area code="96" />
</Region>
<Region name="East">
<Area code="88" />
</Region>
</Employee>
</CompanyInfo>
public class Employee
{
public string EmployeeName { get; set; }
public string DeptId { get; set; }
public List<string> RegionList {get; set;}
}
public class Region
{
public string RegionName { get; set; }
public string AreaCode { get; set; }
}
I am trying to read this XML data, so far I have tried this:
XDocument xml = XDocument.Load(#"C:\data.xml");
var xElement = xml.Element("CompanyInfo");
if (xElement != null)
foreach (var child in xElement.Elements())
{
Console.WriteLine(child.Name);
foreach (var item in child.Attributes())
{
Console.WriteLine(item.Name + ": " + item.Value);
}
foreach (var childElement in child.Elements())
{
Console.WriteLine("--->" + childElement.Name);
foreach (var ds in childElement.Attributes())
{
Console.WriteLine(ds.Name + ": " + ds.Value);
}
foreach (var element in childElement.Elements())
{
Console.WriteLine("------->" + element.Name);
foreach (var ds in element.Attributes())
{
Console.WriteLine(ds.Name + ": " + ds.Value);
}
}
}
}
This enables me to get each node, its attribute name and value and so I can save these data into the relevant field in database, but this seems a long winded way and
not flexible, for instance if the XML structure changes all those foreach statements needs revisiting, also it is difficult to filter the data this way,
I need to write certain if statements to filter the data (e.g get employees from West only etc...)
I was looking for a more flexible way, using linq, something like this:
List<Employees> employees =
(from employee in xml.Descendants("CompanyInfo")
select new employee
{
EmployeeName = employee.Element("employee").Value,
EmployeeDeptId = ?? get data,
RegionName = ?? get data,
AreaCode = ?? get data,,
}).ToList<Employee>();
But I am not sure how I can get the values from the child nodes and apply the filtering (to get the certain employees only). Is this possible? Any help is appreciated.
Thanks
var employees = (from e in xml.Root.Elements("Employee")
let r = e.Element("Region")
where (string)r.Attribute("name") == "West"
select new Employee
{
EmployeeName = (string)e.Attribute("employee"),
EmployeeDeptId = (string)e.Attribute("deptId"),
RegionName = (string)r.Attribute("name"),
AreaCode = (string)r.Element("Area").Attribute("code"),
}).ToList();
But it will still require query revision when XML file structure changes.
Edit
Query for multiple regions per employee:
var employees = (from e in xml.Root.Elements("Employee")
select new Employee
{
EmployeeName = (string)e.Attribute("employee"),
DeptId = (string)e.Attribute("deptId"),
RegionList = e.Elements("Region")
.Select(r => new Region {
RegionName = (string)r.Attribute("name"),
AreaCode = (string)r.Element("Area").Attribute("code")
}).ToList()
}).ToList();
You can then filter the list for employees from given region only:
var westEmployees = employees.Where(x => x.RegionList.Any(r => r.RegionName == "West")).ToList();
You can track the structure:
from employee in xml
.Element("CompanyInfo") // must be root
.Elements("Employee") // only directly children of CompanyInfo
or less strictly
from employee in xml.Descendants("Employee") // all employees at any level
And then get the information you want:
select new Employee
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
RegionName = employee.Element("Region").Attribute("name").Value,
AreaCode = employee.Element("Region").Element("Area").Attribute("code").Value,
}
And with the additional info about multiple regions, assuming a List<Region> Regions property:
select new Employee
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
//RegionName = employee.Element("Region").Attribute("name").Value,
//AreaCode = employee.Element("Region").Element("Area").Attribute("code").Value,
Regions = (from r in employee.Elements("Region") select new Region
{
Name = r.Attribute("name").Value,
Code = r.Element("Area").Attribute("code").Value,
}).ToList();
}
You can do the selection in one query and then the filtering in second or combine them both to one query:
Two queries:
// do te transformation
var employees =
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
Regions = from region in employee.Elements("Region")
select new
{
Name = region.Attribute("name").Value,
AreaCode = region.Element("Area").Attribute("code").Value,
}
};
// now do the filtering
var filteredEmployees = from employee in employees
from region in employee.Regions
where region.AreaCode == "96"
select employee;
Combined one query (same output):
var employees2 =
from selectedEmployee2 in
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = employee.Attribute("name").Value,
EmployeeDeptId = employee.Attribute("deptId").Value,
Regions = from region in employee.Elements("Region")
select new
{
Name = region.Attribute("name").Value,
AreaCode = region.Element("Area").Attribute("code").Value,
}
}
from region in selectedEmployee2.Regions
where region.AreaCode == "96"
select selectedEmployee2;
But there is one little thing you should consider adding. For robustness, you need to check existence of your elements and attributes then the selection will look like that:
var employees =
from employee in xml.Descendants("CompanyInfo").Elements("Employee")
select new
{
EmployeeName = (employee.Attribute("name") != null) ? employee.Attribute("name").Value : string.Empty,
EmployeeDeptId = (employee.Attribute("deptId") != null) ? employee.Attribute("deptId").Value : string.Empty,
Regions = (employee.Elements("Region") != null)?
from region in employee.Elements("Region")
select new
{
Name = (region.Attribute("name")!= null) ? region.Attribute("name").Value : string.Empty,
AreaCode = (region.Element("Area") != null && region.Element("Area").Attribute("code") != null) ? region.Element("Area").Attribute("code").Value : string.Empty,
}
: null
};
<?xml version="1.0"?>
-<bookstore>
<book >
<title>aaaa</title>
-<author >
<first-name>firts</first-name>
<last-name>last</last-name>
</author>
<price>8.23</price>
<otherbooks>
<book >
<title>bbb</title>
<price>18.23</price>
</book>
<book >
<title>ccc</title>
<price>11.22</price>
</book>
</otherbooks>
</book>
</bookstore>
I have selected all books form xml file. How to select title, author( first and last name ) and price for each book with use of XPath?
xPathDoc = new XPathDocument(filePath);
xPathNavigator = xPathDoc.CreateNavigator();
XPathNodeIterator xPathIterator = xPathNavigator.Select("/bookstore//book");
foreach (XPathNavigator book in xPathIterator)
{
??
}
Use SelectSingleNode() and Value:
XPathDocument xPathDoc = new XPathDocument(filePath);
XPathNavigator xPathNavigator = xPathDoc.CreateNavigator();
XPathNodeIterator xPathIterator = xPathNavigator.Select("/bookstore//book");
foreach (XPathNavigator book in xPathIterator)
{
XPathNavigator nav = book.SelectSingleNode("title");
string title = nav==null ? string.Empty : nav.Value;
nav = book.SelectSingleNode("author/first-name");
string authorFirstName = nav==null ? string.Empty : nav.Value;
nav = book.SelectSingleNode("author/last-name");
string authorLastName = nav==null ? string.Empty : nav.Value;
nav = book.SelectSingleNode("price");
string price = nav==null ? string.Empty : nav.Value;;
Console.WriteLine("{0} {1} {2} {3}", title, authorFirstName, authorLastName, price);
}
You can use LINQ2XML
XElement doc=XElement.Load("yourXML.xml");//loads your xml
var bookList=doc.Descendants().Elements("book").Select(
x=>//your book node
new{
title=x.Element("title").Value,
author=new //accessing your author node
{
firstName=x.Element("author").Element("first-name").Value,
lastName=x.Element("author").Element("last-name").Value
},
price=x.Element("price").Value
}
);
bookList now have all the elements you want
So, you can do this now
foreach(var book in bookList)
{
book.title;//contains title of the book
book.author.firstName;//contains firstname of that book's author
book.author.lastName;
}
I like the solution provided by Mimo but with a tiny change, creating an extension method to re-use part of the functionality:
public static class XPathNavigatorExtensions
{
public static string GetChildNodeValue(this XPathNavigator navigator, string nodePath)
{
XPathNavigator nav = navigator.SelectSingleNode(nodePath);
return nav == null ? string.Empty : nav.Value;
}
}
The resulting code will look like cleaner:
ICollection<Book> books = new List<Book>();
foreach (XPathNavigator node in iterator)
{
Book book = new Book() { Author = new Author() };
book.Title = node.GetChildNodeValue("title");
book.Author.FirstName = node.GetChildNodeValue("author/first-name");
book.Author.LastName = node.GetChildNodeValue("author/last-name");
book.Price = node.GetChildNodeValue("price");
books.Add(book);
}