SelectSingleNode Node InnerText property not correct - c#

I am attempting to get the value of a specific node for each parent element found.
In the example I want to return each students First Name.
Instead I am getting the first elements name in each instance. The InnerText of a Student is correct but the InnerText of FirstName is always Alex.
var xml = #"<School>
<Students>
<Student>
<FirstName>Alex</FirstName>
<LastName>Smith</LastName>
<Grade>11</Grade>
</Student>
<Student>
<FirstName>Joanne</FirstName>
<LastName>Robins</LastName>
<Grade>12</Grade>
</Student>
<Student>
<FirstName>Steve</FirstName>
<LastName>Baker</LastName>
<Grade>11</Grade>
</Student>
</Students>
<Teachers>
<Teacher>
<FirstName>George</FirstName>
<LastName>Roberts</LastName>
<Grade>11</Grade>
</Teacher>
<Teacher>
<FirstName>Amanda</FirstName>
<LastName>Walker</LastName>
<Grade>12</Grade>
</Teacher>
<Teacher>
<FirstName>Tracey</FirstName>
<LastName>Smith</LastName>
<Grade>12</Grade>
</Teacher>
</Teachers>
</School>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var resourceTypeNodes = doc.GetElementsByTagName("Student");
var resourceTypesIterator = resourceTypeNodes.GetEnumerator();
while (resourceTypesIterator != null && resourceTypesIterator.MoveNext())
{
var resourceTypeNode = resourceTypesIterator.Current as XmlNode;
var typeNameElement = resourceTypeNode.SelectSingleNode("//FirstName");
Console.WriteLine(resourceTypeNode.InnerXml);
Console.WriteLine(typeNameElement.InnerText);
}
This is the output of the above code.
<FirstName>Alex</FirstName><LastName>Smith</LastName><Grade>11</Grade>
Alex
<FirstName>Joanne</FirstName><LastName>Robins</LastName><Grade>12</Grade>
Alex
<FirstName>Steve</FirstName><LastName>Baker</LastName><Grade>11</Grade>
Alex
What am I missing?

Because you're using //FirstName XPath expression, that will always return first node from root, it doesn't matter if you invoke on children. Just change this:
var typeNameElement = resourceTypeNode.SelectSingleNode("//FirstName");
To this:
var typeNameElement = resourceTypeNode.SelectSingleNode("FirstName");
Moreover is there any specific reason you're manually using IEnumerator? You may simplify your code with foreach:
foreach (XmlNode resourceTypeNode in doc.GetElementsByTagName("Student"))
{
var typeNameElement = resourceTypeNode.SelectSingleNode("FirstName");
Console.WriteLine(resourceTypeNode.InnerXml);
Console.WriteLine(typeNameElement.InnerText);
}

Related

Question with Linq on XML,At least one object must implement IComparable

string studentsXML =
#"<Students>
<Student>
<Name>Toni</Name>
<Age>21</Age>
<University>Yale</University>
<Semester>6</Semester>
<GPA>3.5</GPA>
</Student>
<Student>
<Name>Carla</Name>
<Age>17</Age>
<University>Yale</University>
<Semester>1</Semester>
<GPA>3.8</GPA>
</Student>
<Student>
<Name>Leyla</Name>
<Age>19</Age>
<University>Beijing Tech</University>
<Semester>3</Semester>
<GPA>3.0</GPA>
</Student>
<Student>
<Name>Frank</Name>
<Age>25</Age>
<University>Beijing Tech</University>
<Semester>10</Semester>
<GPA>2.0</GPA>
</Student>
<Student>
<Name>Ken</Name>
<Age>29</Age>
<University>Beijing Tech</University>
<Semester>10</Semester>
<GPA>4.0</GPA>
</Student>
</Students>";
**Above is the XML code and I'm trying to sort it in order of age using linq so it should be able to display name and age in order at the end.
Code below is showing error. Would appreciate it if someone could give me input on this.
I'm actually still learning right now and was trying out stuff but obviously it is not working. **
XDocument studentsXdoc1 = new XDocument();
studentsXdoc1 = XDocument.Parse(studentsXML);
var testing = from student in studentsXdoc1.Descendants("Student")
orderby student.Element("Age")
select new
{
Name = student.Element("Name"),
Age = student.Element("Age")
};
foreach(var i in testing)
{
Console.WriteLine(i);
}
Console.ReadKey();
Just add .Value to student.Element("Age") to get the value of the element
var testing = from student in studentsXdoc1.Descendants("Student")
orderby int.Parse(student.Element("Age").Value)
select new
{
Name = student.Element("Name").Value,
Age = student.Element("Age").Value
};
The value of age needs to be parsed when ordering by age.

LINQ to XML - Unable to read Elements or Descendants from XDocument or XElement

Given below is my XML file:
<?xml version="1.0" encoding="utf-8"?>
<Students>
<Student>
<Ordinal>1</Ordinal>
<Name>Student1</Name>
<BirthDate>Date1</BirthDate>
<ID>ID1</ID>
</Student>
<Student>
<Ordinal>2</Ordinal>
<Name>Student2</Name>
<BirthDate>Date2</BirthDate>
<ID>ID2</ID>
</Student>
</Students>
It's stored locally and loaded:
var path = #"C:\Users\Public\Desktop\LINQ - XML\Students.xml";
XDocument xDoc = XDocument.Load(path);
XElement xEl = XElement.Load(path);
When I print content of either 'XDoc' or 'xEl' to the console: Console.WriteLine(xDoc); I get the full XML file.
But when I try to access (print to console) xEl.Element("Student") or xEl.Descendants("Student") or xEl.Elements() (xDoc also), only thing that gets printed to the console is xContainer info line: System.Xml.Linq.XContainer+<GetElements>d__39
Namespaces should not be a problem but I checked them anyways using both and got blanks.
XNamespace ns = xEl.GetDefaultNamespace(); // For XElement
XNamespace df = xDoc.Root.Name.Namespace; // For XDocument
What would be the way to access those elements?
You already have access to those elememts. There is just a missonception on what Console.WriteLine does .
Console.WriteLinejust call ToString over the thing.
For XNode the ToString is overriden to return a Xml string. source
It will work for xEl.Element as it's return an XElement.
XElement is a XContainer, that is an XNode. So XNode overriden method will be called.
But not for Descendants as it return a collection waiting to be enumerated. Lazy linQ.
In order to display your result you can either enumerate the decendants and call ToString on them.
foreach(var el in xEl.Descendants("Student")){
Console.WriteLine(el);
}
Here is an Example: Live Demo
var input = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Students>
<Student>
<Ordinal>1</Ordinal>
<Name>Student1</Name>
<BirthDate>Date1</BirthDate>
<ID>ID1</ID>
</Student>
<Student>
<Ordinal>2</Ordinal>
<Name>Student2</Name>
<BirthDate>Date2</BirthDate>
<ID>ID2</ID>
</Student>
</Students>";
// Parse string is equivalent to load path.
XDocument xDoc = XDocument.Parse(input);
XElement xEl = XElement.Parse(input);
var xDocString = xDoc.ToString();
var XElElement = xEl.Element("Student").ToString();
// => <Student> <Ordinal>1</Ordinal> ...
var XElDecendant = xEl.Descendants("Student").ToString();
// => System.Xml.Linq.XContainer+<GetDescendants>d__39
// I'm a linQ container containing XNode, please enumerate me.
var xDocElement = xDoc.Element("Student").ToString();
//=> null
var xDocDecendant = xDoc.Descendants("Student").ToString();
// => System.Xml.Linq.XContainer+<GetDescendants>d__39
// I'm a linQ container containing XNode, please enumerate me.
foreach(var el in xEl.Descendants("Student")){
Console.WriteLine(el);
}
Console.WriteLine expects a string. If you pass in an object to this method, the object should override the ToString method, otherwise it will print the fully qualified object name.
Based on your above statement, it looks like the XDocument object overrides the ToString() method to allow the Console to print XML content as a string. If your return type is System.Xml.Linq.XContainer+<GetElements> try using LINQ to convert the object and print to the console.
You can't print the collection of XElements which is an XContainer object. You need to iterate through each XElement and print it.
The below LINQ query should work:
xEl.Elements().ToList().ForEach(Console.WriteLine);
to get the first student
XDocument data = XDocument.Load("students.xml");
Console.WriteLine(data.Root.Elements().ElementAt(0));
to get the node i
Console.WriteLine(data.Root.Elements().ElementAt(0).Nodes().ElementAt(i));

Merge parent nodes if child nodes identical xml

Let me put the scenario:
If the 'Student' node has same child elements then merge the 'Student' node. In this case if the 'Name' node is found in other 'Student' nodes with same value then those 2 'Student' nodes need to be merged with unique elements. In this case the 'Name' node being identical comes 1 time and the 'Address' node coming 2 times.
Also the input xml can have different set of child nodes and can have different names every time.
Below is Input xml
<Root>
<Student>
<Name>Tim</Name>
<Address>
<City>
<Location1>MEL</Location1>
</City>
</Address>
</Student>
<Student>
<Name>Tim</Name>
<Address>
<City>
<Location1>DEL</Location1>
</City>
</Address>
</Student>
<Student>
<Name>1</Name>
<FatherName>Papa</FatherName>
<Address>
<Suburb>1</Suburb>
<City>
<Location1>HNL</Location1>
</City>
</Address>
</Student>
<Student>
<Name>1</Name>
<MotherName>Mom</MotherName>
<Address>
<City>
<Location1>HNL</Location1>
</City>
</Address>
</Student>
</Root>
Expected xml:
<Root>
<Student>
<Name>Tim</Name>
<Address>
<City>
<Location1>MEL</Location1>
</City>
</Address>
<Address>
<City>
<Location1>DEL</Location1>
</City>
</Address>
</Student>
<Student>
<Name>1</Name>
<FatherName>Papa</FatherName>
<Address>
<Suburb>1</Suburb>
<City>
<Location1>HNL</Location1>
</City>
</Address>
</Student>
<Student>
<Name>1</Name>
<MotherName>Mom</MotherName>
<Address>
<City>
<Location1>HNL</Location1>
</City>
</Address>
</Student>
</Root>
I tried to implement with the below code. I know its not very efficient.
var newdoc = XDocument.Parse(input);
// 'restriction' is the concerned node
foreach (var element in newdoc.Descendants("restriction"))
{
if (skiptimes > 0)
{
skiptimes--;
continue;
}
//Get distinct node names for 'element'
var distinctNodeName = element.Elements().Select(cc => cc.Name).Distinct();
//delete if found 'freetext' node as the this do not need to be comapared
distinctNodeName = distinctNodeName.Where(n => n.LocalName.ToString() != "FreeText");
//Get distinct elements
var distinctElementName = element.Elements().Select(xx => xx).Distinct();
foreach (var nextelement in element.ElementsAfterSelf())
{
if (!nextelement.IsEmpty)
{
//Get distinct node names for 'nextelement'
var distinctNodeName2 = nextelement.Elements().Select(xx => xx.Name).Distinct();
//delete if found 'freetext' node as the this do not need to be comapared
distinctNodeName2 = distinctNodeName2.Where(n => n.LocalName.ToString() != "FreeText");
//Get distinct elements
var distinctElements2 = nextelement.Elements().Select(xx => xx).Distinct();
//From 'element' excluding the 'StopoverSegs' node which by default always come as last node
var subelements = element.Elements().Take(distinctNodeName.Count() - 1);
//From 'nextelement' excluding the 'StopoverSegs' node which by default always come as last node
var sub2 = nextelement.Elements().Take(distinctNodeName2.Count() - 1);
// Compare node name counts which are selected as distinct
if (distinctNodeName.Count() == distinctNodeName2.Count())
{
ArrayList fir = new ArrayList();
int arrcount = 0; ArrayList sec = new ArrayList();
//Add 'element' to array list for comparison
foreach (var firstSet in subelements)
{
fir.Add(firstSet.ToString()); arrcount++;
}
//Add 'nextelement' to array list for comparison
foreach (var secondSet in sub2)
{
sec.Add(secondSet.ToString());
}
//comparison . I could not get through to compare via SequenceEqual or other custom
//extension
for (int i = 0; i < arrcount; i++)
{
if (fir[i].ToString().Trim() != sec[i].ToString().Trim())
{
GotEqual = false;
break;
}
else
{
GotEqual = true;
}
}
if (GotEqual)
{
element.Add(nextelement.Elements().Except(element.Elements(), new ElementComparer()));
skiptimes++;
}
else
{
break;
}
}
}
}
finalXML.Add(element);
}
This is horrible code, and I haven't had time to clean it up, but this works.
var xDoc = XDocument.Parse("<and><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>4</NbMaxOfStops><Charges1><FirstAmount Currency=\"AUD\">10.00</FirstAmount></Charges1><StopoversSegs><GeoSpec><Location1>AKL</Location1><Location2>CHC</Location2></GeoSpec><ChargeIndicator>1</ChargeIndicator></StopoversSegs><StopoversSegs><GeoSpec><Location1>LAX</Location1><Location2>RAR</Location2></GeoSpec><ChargeIndicator>1</ChargeIndicator></StopoversSegs></restriction><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>2</NbMaxOfStops><NbOfOutboundStops>1</NbOfOutboundStops><NbOfInboundStops>1</NbOfInboundStops><Charges1><FirstAmountNbOf>1</FirstAmountNbOf><FirstAmount Currency=\"AUD\">301.00</FirstAmount><AdditionalAmountNbOf>1</AdditionalAmountNbOf><AdditionalAmount Currency=\"AUD\">151.00</AdditionalAmount></Charges1><StopoversSegs><NbOfStops>1</NbOfStops><GeoSpec><Location1>LAX</Location1></GeoSpec><InboundOutboundIndicator>I</InboundOutboundIndicator><ChargeIndicator>1</ChargeIndicator></StopoversSegs></restriction><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>2</NbMaxOfStops><NbOfOutboundStops>1</NbOfOutboundStops><NbOfInboundStops>1</NbOfInboundStops><Charges1><FirstAmountNbOf>1</FirstAmountNbOf><FirstAmount Currency=\"AUD\">301.00</FirstAmount><AdditionalAmountNbOf>1</AdditionalAmountNbOf><AdditionalAmount Currency=\"AUD\">151.00</AdditionalAmount></Charges1><StopoversSegs><NbOfStops>1</NbOfStops><GeoSpec><Location1>SFO</Location1></GeoSpec><InboundOutboundIndicator>O</InboundOutboundIndicator><ChargeIndicator>2</ChargeIndicator></StopoversSegs></restriction></and>");
var xDoc2 = XDocument.Parse("<and><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>4</NbMaxOfStops><Charges1><FirstAmount Currency=\"AUD\">10.00</FirstAmount></Charges1><StopoversSegs><GeoSpec><Location1>AKL</Location1><Location2>CHC</Location2></GeoSpec><ChargeIndicator>1</ChargeIndicator></StopoversSegs><StopoversSegs><GeoSpec><Location1>LAX</Location1><Location2>RAR</Location2></GeoSpec><ChargeIndicator>1</ChargeIndicator></StopoversSegs></restriction><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>2</NbMaxOfStops><NbOfOutboundStops>1</NbOfOutboundStops><NbOfInboundStops>1</NbOfInboundStops><Charges1><FirstAmountNbOf>1</FirstAmountNbOf><FirstAmount Currency=\"AUD\">301.00</FirstAmount><AdditionalAmountNbOf>1</AdditionalAmountNbOf><AdditionalAmount Currency=\"AUD\">151.00</AdditionalAmount></Charges1><StopoversSegs><NbOfStops>1</NbOfStops><GeoSpec><Location1>LAX</Location1></GeoSpec><InboundOutboundIndicator>I</InboundOutboundIndicator><ChargeIndicator>1</ChargeIndicator></StopoversSegs></restriction><restriction type=\"Stopovers_Type\" Name=\"STP\"><NbMaxOfStops>2</NbMaxOfStops><NbOfOutboundStops>1</NbOfOutboundStops><NbOfInboundStops>1</NbOfInboundStops><Charges1><FirstAmountNbOf>1</FirstAmountNbOf><FirstAmount Currency=\"AUD\">301.00</FirstAmount><AdditionalAmountNbOf>1</AdditionalAmountNbOf><AdditionalAmount Currency=\"AUD\">151.00</AdditionalAmount></Charges1><StopoversSegs><NbOfStops>1</NbOfStops><GeoSpec><Location1>SFO</Location1></GeoSpec><InboundOutboundIndicator>O</InboundOutboundIndicator><ChargeIndicator>2</ChargeIndicator></StopoversSegs></restriction></and>");
xDoc.Root.Descendants().Where(w => w.Name == "StopoversSegs").Remove();
var lsDistinct = xDoc.Root.Elements().Select(s => s.ToString()).Distinct().ToList();
var lDistinct = lsDistinct.Select(s => Tuple.Create(s, XElement.Parse(s))).ToList();
var lStopNodes = xDoc2.Root.Elements().Select(s => Tuple.Create(XElement.Parse(s.ToString()), XElement.Parse(s.ToString()))).ToList();
foreach ( var stopNode in lStopNodes)
{
stopNode.Item1.Descendants("StopoversSegs").Remove();
}
var lStopNodesToDistinct = lStopNodes.Select(s => Tuple.Create(s.Item1.ToString(), s.Item2.Descendants("StopoversSegs"))).ToList();
foreach (var distinct in lDistinct)
{
distinct.Item2.Add(lStopNodesToDistinct.Where(w => w.Item1 == distinct.Item1).Select(s => s.Item2.Nodes()).ToArray());
var test = lStopNodesToDistinct.Where(w => w.Item1 == distinct.Item1).Select(s => s.Item2.Nodes()).ToArray();
}
xDoc.Root.Elements().Remove();
xDoc.Root.Add(lDistinct.Select(s => s.Item2));

Parsing XML Element Once

I have a peace of xml which I need to gather information off.
<response>
<students>
<student>
<educationalinstitutionname>Test One</educationalinstitutionname>
<academicqualificationtype>TestLevel One</academicqualificationtype>
<starttime>22/02/06</starttime>
<endtime>19/08/10</endtime>
<grade>A</grade>
</student>
<student>
<educationalinstitutionname>Test One</educationalinstitutionname>
<academicqualificationtype>TestLevel Two</academicqualificationtype>
<starttime>22/02/06</starttime>
<endtime>19/08/10</endtime>
<grade>B</grade>
</student>
<student>
<educationalinstitutionname>Test Two</educationalinstitutionname>
<academicqualificationtype>TestLevel Two</academicqualificationtype>
<starttime>22/02/06</starttime>
<endtime>19/08/10</endtime>
<grade>C</grade>
</student>
<student>
<educationalinstitutionname>Test Two</educationalinstitutionname>
<academicqualificationtype>TestLevel Three</academicqualificationtype>
<starttime>22/02/06</starttime>
<endtime>19/08/10</endtime>
<grade>D</grade>
</student>
</students>
The issue which I am having is that I need to parse the educationalinstitutionname once if it the same but gather the academicqualificationtype, starttime, endtime and grade for everything that it appears under the same educationalinstitutionname node.
XmlNodeList WordXMLNodeLists = XmlResponceDoc.SelectNodes("/response/students/student");
string GetDetaisl = string.Empty;
foreach (XmlNode GradeItem in WordXMLNodeLists)
{
string GetStartDate = EducationElement.SelectNodes("/response/students/student/starttime")[0].InnerText;
string GetEndDate = EducationElement.SelectNodes("/response/students/student/endtime")[0].InnerText;
string GetEstablishmentName = EducationElement.GetElementsByTagName("educationalinstitutionname")[0].InnerText;
string GetGrade = EducationElement.SelectNodes("/response/students/student/grade")[0].InnerText;
GetDetaisl = string.Concat(GetStartDate, " ", GetEndDate, " ", GetEstablishmentName);
if (GetDetaisl.Length == GetEstablishmentName.Length)
{
string GetGrades = GetGrade;
GetEducationNodeDate.Add(GetGrades);
}
else
{
GetEducationNodeDate.Add(GetDetaisl);
continue;
}
}
The issue which I am finding is that I cannot seem to loop through the name once and gather all the information in the document under that name for the establishment. Thanks for any help which you can provide
It isn't very clear what you're trying to achieve specifically. I assumed that basically you want value of most of nodes within <student> tag :
XmlNodeList WordXMLNodeLists = XmlResponceDoc.SelectNodes("/response/students/student");
foreach (XmlNode GradeItem in WordXMLNodeLists)
{
string GetStartDate = GradeItem.SelectSingleNode("./starttime").InnerText;
string GetEndDate = GradeItem.SelectSingleNode("./endtime").InnerText;
string GetEstablishmentName = GradeItem.SelectSingleNode("./educationalinstitutionname").InnerText;
string GetGrade = GradeItem.SelectSingleNode("./grade").InnerText;
//do something with data gathered above
}

Unable to extract value from XDocument using xpath

I have a simple xml of users. I have a StudentId, I just need to get the student name from the xml on the basis of studentid. Seems to be simple but I am unable to get it done using xpath.
Here's the xml:
<Students>
<Student>
<StudentId>1</StudentId>
<StudentName>Mad</StudentName>
</Student>
<Student>
<StudentId>2</StudentId>
<StudentName>Cad</StudentName>
</Student>
</Students>
Here's my code:
XDocument xmldoc = XDocument.Load(Server.MapPath("~/xmlsample.xml"));
string StudentId = "2"; // id to be selected
var username = xmldoc.XPathSelectElement("Students/Student/StudentName").Value;// Not sure how to use where condition here
You just need to filter by studentId. Should be:
var username = xmldoc.XPathSelectElement(String.Format("Students/Student[StudentId={0}]/StudentName", StudentId)).Value;

Categories