have the following linq code, trying to parse an xml file to a datatable but i get strange values in the resultant datatable all cell values show as
System.Xml.Ling.XContainer+<GetElements>d_11
Here is my LINQ
XDocument doc1 = XDocument.Load(#"D:\m.xml");
var q = from address in doc1.Root.Elements("Address")
let name = address.Elements("Name")
let street = address.Elements("Street")
let city = address.Elements("city")
select new
{
Name = name,
Street = street,
City = city
};
var xdt = new DataTable();
xdt.Columns.Add("Name", typeof(string));
xdt.Columns.Add("Street", typeof(string));
xdt.Columns.Add("City", typeof(string));
foreach (var address in q)
{
xdt.Rows.Add(address.Name, address.Street, address.City);
}
dataGrid1.ItemsSource = xdt.DefaultView;
here is my xml:
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
</PurchaseOrder>
and here is the result i get!
You forgot to regrieve the inner text of XElements. So you are selecting the whole element with attributes etc. Use this part of code:
var q = from address in doc1.Root.Elements("Address")
let name = address.Element("Name")
let street = address.Element("Street")
let city = address.Element("city")
select new
{
Name = name.Value,
Street = street.Value,
City = city.Value
};
address.Elements("Name") is a collection of all of the elements of type "Name". It so happens that in your case it's a collection of size one, but it's still a collection. You want to get the first item out of that collection (since you know it will be the only one) and then get the text value of that element. If you use Element instead of Elements you'll get the first item that matches, rather than a collection of items.
Now that you have your single element, you also need to get the value of that element, rather than the element itself (which also contains lots of other information in the general case, even though there really isn't anything else interesting about it in this particular case.
var q = from address in doc1.Root.Elements("Address")
select new
{
Name = address.Element("Name").Value,
Street = address.Element("Street").Value,
City = address.Element("City").Value
};
How does this work for you?
var q = from address in doc1.Root.Elements("Address")
let name = (string)(address.Element("Name"))
let street = (string)(address.Element("Street"))
let city = (string)(address.Element("city"))
//...
http://msdn.microsoft.com/en-us/library/bb155263.aspx
You are calling Elements which returns n elements wrapped in a helper class.
You probably mean to call Element which returns the first element as an XElement object.
Try this:
var q = from address in doc1.Root.Elements("Address")
let name = address.Element("Name").Value
let street = address.Element("Street").Value
let city = address.Element("City").Value
Change address.Elements("Name") to address.Elements("Name").FirstOrDefault() and so on.
The Elements method returns an IEnumerable. Therefore, your let variables point ato a sequence of elements, not a single element. You should take the single element returned, which will be an XElement, and then take its Value property to get the concatenated text of its contents. (As per documentation)
Instead of
select new
{
Name = name,
Street = street,
City = city
}
You should write:
select new
{
Name = name.Single().Value,
Street = street.Single().Value,
City = city.Single().Value
}
Either there, or directly in the let expressions. You may also find a helper method useful:
public static string StringValueOfElementNamed(XElement node, string elementName) {
return node.Elements(elementName).Single().Value;
}
Turn this helper method into an extension method if you wish to use member access syntax.
Edit: After reading concurrent answers, the better method to use would be:
public static string StringValueOfElementNamed(XElement node, string elementName) {
return node.Element(elementName).Value;
}
Element returns the first found element. Beware the null pointer returned when there is no element found.
Related
How can find out result of this:
<People>
<Person name="John" />
<Person name="Andrew" />
</People>
I need to find out if person with specific name exists in People list.
Example:
if (Element("People").ForAny(person => person.name == "John")) // Returns True
if (Element("People").ForAny(person => person.name == "Amanda")) // Returns False
I am using Xml Linq library.
Thanks for any help!
You need to work on your separation of concerns: divide your problems into smaller problems that can be used independently. This make it easier to reuse your code for other problems, easier to test your code, make changes without breaking the code.
Your problem consists of two subproblems:
How to convert XML data to a sequence of Persons
How to extract Persons with a certain name from a sequence of Persons
How to detect if a sequence of Persons is not empty?
I don't often convert XML to IEnumerable, but this answer helped me.
class Person
{
public string Name {get; set;}
... // other properties
}
If in your XML a Person really only has a Name, you don't have to define a special class for it. But then again: are you sure that a Person is nothing more than a Name? Shouldn't that be a collection of Names?
Ok, so we have a Person class (or a string), and we need a function to convert the XML into a sequence of Persons:
Write this as an extension method, so we can let it look like LINQ. See extension methods demystified. The word this in the signature makes it an extension method. It allows you to use the method as if it was a method of the string class itself
public static IEnumerable<Person> ToPersons(this String xml)
{
return XElement.Parse(xml)
.Elements("Person")
.Select(xmlElement => new Person
{
Name = xmlElement.Element("Name").Value,
// if needed, fill other properties, for instance:
Id = Int32.Parse(xmlElement.Element("Id").Value),
});
}
Usage:
string xmlTxt = ...
IEnumerable<Person> persons = xmlTxt.ToPersons();
If you don't have a string with xml, but an XmlReader, consider:
public static IEnumerable<Person> ToPersons(this XmlReader xmlReader)
{
while(xmlReader.Read())
{
// TODO: read one Person from xmlReader
Person person = new Person() {...} // you know better how to do this than I
yield return person
}
Now that we have seperated xml conversion from Person handling, the rest is easy:
Requirement:
Given the name of a Person, and some XmlText that represents a sequence of Persons, tell me if a Person with this name is in this sequence
string personName = ...
string xmlText = ... // or Use the XmlReader
bool personAvailable = xmlText.ToPersons()
.Where(person => person.Name == personName)
.Any();
In words: convert the xmlText to a sequence of Persons. From this sequence keep only those Persons that have a Name equal to personName. Return true if there is any element left in the remaining sequence.
If you use peculiar characters, or want to ignore case. Consider:
IEqualityComparer<strong> personNameComparer = StringComparer.CurrentCultureIgnoreCase;
bool personAvailable = xmlText.ToPersons()
.Where(person => personNameComparer.Equals(person.Name, personName))
.Any();
The nice thing is, because you separated your concerns, you can use ToPersons also for other functions:
Requirement: give me all New York City Residents, born before 1960
var oldPersons = xmlText.ToPersons
.Where(person => person.Location == "New York City" && person.Birthday.Year < 1960);
Or if you get your Persons from a Json file, or a CSV-file, or a database, you can still get all all New York City Residents, born before 1960, while only writing a new ToPersons method.
You can simply navigate from the root element People.
In all its decendants Person, is there any with the attribute name is equal to the value you are looking for :
string input = #"<People>
<Person name=""John"" />
<Person name=""Andrew"" />
</People>";
XDocument doc = XDocument.Parse(input);
var isJohnHere = doc.Element("People")
.Descendants("Person")
.Any(x=> x.Attribute("name").Value == "John");
The same way getting the List<string> of people's names will be :
var names = doc.Element("People")
.Descendants("Person")
.Select(x=> x.Attribute("name").Value);
And even shorter " All the Persons no matter where there are" :
var isJohnHere2 = doc.Descendants("Person")
.Any(x=> x.Attribute("name").Value == "John");
Live demo
I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;
In above figure the element Chandru is repeated as two times.
so i have to count the repeated element.
But i don't know to get count of repeated element.
please help me.
Here the code what i wrote
public XML_3()
{
this.InitializeComponent();
XmlDocument doc = new XmlDocument();
doc.Load("D:/student_2.xml");
XmlNodeList student_list = doc.GetElementsByTagName("Student");
foreach (XmlNode node in student_list)
{
XmlElement student = (XmlElement)node;
string sname = student.GetElementsByTagName("Chandru")[0].InnerText;
string fname = student.GetElementsByTagName("FName")[0].InnerText;
string id = student.GetElementsByTagName("Chandru")[0].Attributes["ID"].InnerText;
Window.Content = sname + fname + id;
}
}
var count = student.GetElementsByTagName("Chandru").Count;
I think it would be easier using LINQ to XML and X-Classes:
var doc = XDocument.Load("D:/student_2.xml");
var results = (from s in doc.Root.Elements()
group s by s.Name into g
select new { Name = g.Key, Count = g.Count() }).ToList();
this will give you a list of elements with two properties each: Name and Count. If you want to receive only that names, which occurred more than once you can add where g.Count() > 1 between group and select statement.
I am not a C# programmer, but hence, I can give you the algorithm then you can try to apply it on your program.
Define an array of struct, the struct must have two fields: ElmntName and NbOccurance.
struct Elmnt
{ String ElmntName;
Int NbOccurance;
} MyElement;
Then each element you pass thorugh, pass through your array, if the elementwas not found, save it as new element with NbOccurance=0;
Else, if it was found increment the number of occursnces.
At the end of reading your xml, you will get a list containing the names of the elements and their occurances.
I have a string list(A) of individualProfileId's (GUID) that can be in any order(used for displaying personal profiles in a specific order based on user input) which is stored as a string due to it being part of the cms functionality.
I also have an asp c# Repeater that uses a LinqDataSource to query against the individual table. This repeater needs to use the ordered list(A) to display the results in the order specified.
Which is what i am having problems with. Does anyone have any ideas?
list(A)
'CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC',
'7FF2D867-DE88-4549-B5C1-D3C321F8DB9B',
'3FC3DE3F-7ADE-44F1-B17D-23E037130907'
Datasource example
IndividualProfileId Name JobTitle EmailAddress IsEmployee
3FC3DE3F-7ADE-44F1-B17D-23E037130907 Joe Blo Director dsd#ad.com 1
CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC Maxy Dosh The Boss 1
98AB3AFD-4D4E-4BAF-91CE-A778EB29D959 some one a job 322#wewd.ocm 1
7FF2D867-DE88-4549-B5C1-D3C321F8DB9B Max Walsh CEO 1
There is a very simple (single-line) way of doing this, given that you get the employee results from the database first (so resultSetFromDatabase is just example data, you should have some LINQ query here that gets your results).
var a = new[] { "GUID1", "GUID2", "GUID3"};
var resultSetFromDatabase = new[]
{
new { IndividualProfileId = "GUID3", Name = "Joe Blo" },
new { IndividualProfileId = "GUID1", Name = "Maxy Dosh" },
new { IndividualProfileId = "GUID4", Name = "some one" },
new { IndividualProfileId = "GUID2", Name = "Max Walsh" }
};
var sortedResults = a.Join(res, s => s, e => e.IndividualProfileId, (s, e) => e);
It's impossible to have the datasource get the results directly in the right order, unless you're willing to write some dedicated SQL stored procedure. The problem is that you'd have to tell the database the contents of a. Using LINQ this can only be done via Contains. And that doesn't guarantee any order in the result set.
Turn the list(A), which you stated is a string, into an actual list. For example, you could use listAsString.Split(",") and then remove the 's from each element. I’ll assume the finished list is called list.
Query the database to retrieve the rows that you need, for example:
var data = db.Table.Where(row => list.Contains(row.IndividualProfileId));
From the data returned, create a dictionary keyed by the IndividualProfileId, for example:
var dic = data.ToDictionary(e => e.IndividualProfileId);
Iterate through the list and retrieve the dictionary entry for each item:
var results = list.Select(item => dic[item]).ToList();
Now results will have the records in the same order that the IDs were in list.
I have the following method that is supposed to parse information from an XML response and return a collection of users.
I've opted into creating a Friend class and returning a List<Friend> to the calling method.
Here's what I have so far, but I noticed that the ids.ToList().Count method parses every single id element to a List, then does it again in the for conditional. It's just super ineffective.
public List<Friend> FindFriends()
{
List<Friend> friendList = new List<Friend>();
var friends = doc.Element("ipb").Element("profile").Element("friends").Elements("user");
var ids = from fr in friends
select fr.Element("id").Value;
var names = from fr in friends
select fr.Element("name").Value;
var urls = from fr in friends
select fr.Element("url").Value;
var photos = from fr in friends
select fr.Element("photo").Value;
if (ids.ToList().Count > 0)
{
for (int i = 0; i < ids.ToList().Count; i++)
{
Friend buddy = new Friend();
buddy.ID = ids.ToList()[i];
buddy.Name = names.ToList()[i];
buddy.URL = urls.ToList()[i];
buddy.Photo = photos.ToList()[i];
friendList.Add(buddy);
}
}
return friendList;
}
First question - do you have to return a List<Friend>? Can you return an IEnumerable<Friend> instead? If so, performance gets a lot better:
IEnumerable<Friend> FindFriends()
{
return doc.Descendants("user").Select(user => new Friend {
ID = user.Element("id").Value,
Name = user.Element("name").Value,
Url = user.Element("url").Value,
Photo = user.Element("photo").Value
});
}
Rather than actually creating new buckets and stuffing values into them, this creates a projection, or a new object that simply contains all of the logic for how to create the new Friend objects without actually creating them. They get created when the caller eventually starts to foreach over the IEnumerable. This is called "deferred execution".
This also makes one assumption - All the <user> nodes in your XML fragment are friends. If that isn't true, the first part of the XML selection might need to be a little more complex.
And as #anon points out, even if you do need to return a List<Friend> for some reason not obvious from the information you've provided, you can just call .ToList() at the end of the return statement. This will just execute the projection I described above straight into a new bucket, so you only ever create one.
Why do you need the separate ids/names/urls/photos variables? Combine it all. You can eliminate the ToList() call if you don't need a List.
List<Friend> friendList = (from f in doc.Element("ipb").Element("profile").Element("friends").Elements("user")
select new Friend() {
ID = f.Element("id").Value,
Name = f.Element("name").Value,
URL = f.Element("url").Value,
Photo = f.Element("photo").Value
}).ToList();
return friendList;